From a94774a1892abd57b377986e9c7db909bbe4b83b Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 20 Jul 2018 17:59:52 -0700 Subject: [PATCH] Add codefix to generate types for untyped module --- package.json | 1 + src/compiler/core.ts | 10 +- src/compiler/diagnosticMessages.json | 16 +- src/compiler/factory.ts | 2 +- src/compiler/inspectValue.ts | 158 +++++ src/compiler/moduleNameResolver.ts | 13 +- src/compiler/tsconfig.json | 3 +- src/harness/fourslash.ts | 33 +- src/jsTyping/shared.ts | 1 + src/jsTyping/types.ts | 19 +- src/server/project.ts | 9 + src/server/session.ts | 4 +- src/server/typingsCache.ts | 7 + src/services/codefixes/fixCannotFindModule.ts | 144 +++- src/services/codefixes/generateTypes.ts | 227 ++++++ src/services/services.ts | 31 +- src/services/textChanges.ts | 56 +- src/services/tsconfig.json | 3 +- src/services/types.ts | 52 +- .../unittests/convertToAsyncFunction.ts | 2 +- .../unittests/extractTestHelpers.ts | 25 +- src/testRunner/unittests/organizeImports.ts | 4 +- src/testRunner/unittests/textChanges.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 3 +- src/tsserver/server.ts | 22 +- src/typingsInstaller/nodeTypingsInstaller.ts | 5 + .../reference/api/tsserverlibrary.d.ts | 20 +- tests/baselines/reference/api/typescript.d.ts | 19 +- .../reference/generateTypes/global.d.ts | 236 +++++++ .../reference/generateTypes/lodash.d.ts | 668 ++++++++++++++++++ .../fourslash/codeFixCannotFindModule_all.ts | 5 +- .../codeFixCannotFindModule_generateTypes.ts | 44 ++ ...deFixCannotFindModule_generateTypes_all.ts | 47 ++ ...generateTypes_noExistingCompilerOptions.ts | 28 + ..._generateTypes_notForNonexistentPackage.ts | 13 + ...dule_generateTypes_typesDirectoryExists.ts | 30 + .../fourslash/codeFixGenerateDefinitions.ts | 9 + .../codeFixUndeclaredMethodFunctionArgs.ts | 2 +- tests/cases/fourslash/formatWithTabs.ts | 2 +- tests/cases/fourslash/fourslash.ts | 11 +- tests/cases/fourslash/generateTypes.ts | 109 +++ .../fourslash/generateTypes_baselines.ts | 35 + .../cases/fourslash/generateTypes_classes.ts | 109 +++ 43 files changed, 2120 insertions(+), 119 deletions(-) create mode 100644 src/compiler/inspectValue.ts create mode 100644 src/services/codefixes/generateTypes.ts create mode 100644 tests/baselines/reference/generateTypes/global.d.ts create mode 100644 tests/baselines/reference/generateTypes/lodash.d.ts create mode 100644 tests/cases/fourslash/codeFixCannotFindModule_generateTypes.ts create mode 100644 tests/cases/fourslash/codeFixCannotFindModule_generateTypes_all.ts create mode 100644 tests/cases/fourslash/codeFixCannotFindModule_generateTypes_noExistingCompilerOptions.ts create mode 100644 tests/cases/fourslash/codeFixCannotFindModule_generateTypes_notForNonexistentPackage.ts create mode 100644 tests/cases/fourslash/codeFixCannotFindModule_generateTypes_typesDirectoryExists.ts create mode 100644 tests/cases/fourslash/codeFixGenerateDefinitions.ts create mode 100644 tests/cases/fourslash/generateTypes.ts create mode 100644 tests/cases/fourslash/generateTypes_baselines.ts create mode 100644 tests/cases/fourslash/generateTypes_classes.ts diff --git a/package.json b/package.json index 510d3aab53b1b..8425b84380528 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "gulp-typescript": "latest", "istanbul": "latest", "jake": "latest", + "lodash": "4.17.10", "merge2": "latest", "minimist": "latest", "mkdirp": "latest", diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1eaab5b1b59e0..35954d55a8b81 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1414,9 +1414,12 @@ namespace ts { /** * Tests whether a value is string */ - export function isString(text: any): text is string { + export function isString(text: unknown): text is string { return typeof text === "string"; } + export function isNumber(x: unknown): x is number { + return typeof x === "number"; + } export function tryCast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined; export function tryCast(value: T, test: (value: T) => boolean): T | undefined; @@ -1539,6 +1542,7 @@ namespace ts { * Every function should be assignable to this, but this should not be assignable to every function. */ export type AnyFunction = (...args: never[]) => void; + export type AnyConstructor = new (...args: unknown[]) => unknown; export namespace Debug { export let currentAssertionLevel = AssertionLevel.None; @@ -2130,4 +2134,8 @@ namespace ts { deleted(oldItems[oldIndex++]); } } + + export function fill(length: number, cb: (index: number) => T): T[] { + return new Array(length).fill(0).map((_, i) => cb(i)); + } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 32a07c0818dcf..86f09f1e07fc2 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4559,7 +4559,7 @@ "category": "Message", "code": 95062 }, - + "Add missing enum member '{0}'": { "category": "Message", "code": 95063 @@ -4573,7 +4573,15 @@ "code": 95065 }, "Convert all to async functions": { - "category": "Message", - "code": 95066 + "category": "Message", + "code": 95066 + }, + "Generate types for '{0}'": { + "category": "Message", + "code": 95067 + }, + "Generate types for all packages without types": { + "category": "Message", + "code": 95068 } -} +} \ No newline at end of file diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 4bb374682e087..0f9cf140a1e64 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -235,7 +235,7 @@ namespace ts { // Modifiers - export function createModifier(kind: T) { + export function createModifier(kind: T): Token { return createToken(kind); } diff --git a/src/compiler/inspectValue.ts b/src/compiler/inspectValue.ts new file mode 100644 index 0000000000000..219402d16f1d3 --- /dev/null +++ b/src/compiler/inspectValue.ts @@ -0,0 +1,158 @@ +/* @internal */ +namespace ts { + export interface InspectValueOptions { + readonly fileNameToRequire: string; + } + + export const enum ValueKind { Const, Array, FunctionOrClass, Object } + export interface ValueInfoBase { + readonly name: string; + } + export type ValueInfo = ValueInfoSimple | ValueInfoArray | ValueInfoFunctionOrClass | ValueInfoObject; + export interface ValueInfoSimple extends ValueInfoBase { + readonly kind: ValueKind.Const; + readonly typeName: string; + readonly comment?: string | undefined; + } + export interface ValueInfoFunctionOrClass extends ValueInfoBase { + readonly kind: ValueKind.FunctionOrClass; + readonly source: string | number; // For a native function, this is the length. + readonly prototypeMembers: ReadonlyArray; + readonly namespaceMembers: ReadonlyArray; + } + export interface ValueInfoArray extends ValueInfoBase { + readonly kind: ValueKind.Array; + readonly inner: ValueInfo; + } + export interface ValueInfoObject extends ValueInfoBase { + readonly kind: ValueKind.Object; + readonly members: ReadonlyArray; + } + + export function inspectModule(fileNameToRequire: string): ValueInfo { + return inspectValue(removeFileExtension(getBaseFileName(fileNameToRequire)), tryRequire(fileNameToRequire)); + } + + export function inspectValue(name: string, value: unknown): ValueInfo { + return getValueInfo(name, value, getRecurser()); + } + + type Recurser = (obj: unknown, name: string, cbOk: () => T, cbFail: (isCircularReference: boolean, keyStack: ReadonlyArray) => T) => T; + function getRecurser(): Recurser { + const seen = new Set(); + const nameStack: string[] = []; + return (obj, name, cbOk, cbFail) => { + if (seen.has(obj) || nameStack.length > 4) { + return cbFail(seen.has(obj), nameStack); + } + + seen.add(obj); + nameStack.push(name); + const res = cbOk(); + nameStack.pop(); + seen.delete(obj); + return res; + }; + } + + function getValueInfo(name: string, value: unknown, recurser: Recurser): ValueInfo { + return recurser(value, name, + (): ValueInfo => { + if (typeof value === "function") return getFunctionOrClassInfo(value as AnyFunction, name, recurser); + if (typeof value === "object") { + const builtin = getBuiltinType(name, value as object, recurser); + if (builtin !== undefined) return builtin; + const entries = getEntriesOfObject(value as object); + return { kind: ValueKind.Object, name, members: flatMap(entries, ({ key, value }) => getValueInfo(key, value, recurser)) }; + } + return { kind: ValueKind.Const, name, typeName: isNullOrUndefined(value) ? "any" : typeof value }; + }, + (isCircularReference, keyStack) => anyValue(name, ` ${isCircularReference ? "Circular reference" : "Too-deep object hierarchy"} from ${keyStack.join(".")}`)); + } + + function getFunctionOrClassInfo(fn: AnyFunction, name: string, recurser: Recurser): ValueInfoFunctionOrClass { + const prototypeMembers = getPrototypeMembers(fn, recurser); + const namespaceMembers = flatMap(getEntriesOfObject(fn), ({ key, value }) => getValueInfo(key, value, recurser)); + const toString = cast(Function.prototype.toString.call(fn), isString); + const source = stringContains(toString, "{ [native code] }") ? getFunctionLength(fn) : toString; + return { kind: ValueKind.FunctionOrClass, name, source, namespaceMembers, prototypeMembers }; + } + + const builtins: () => ReadonlyMap = memoize(() => { + const map = createMap(); + for (const { key, value } of getEntriesOfObject(global)) { + if (typeof value === "function" && typeof value.prototype === "object" && value !== Object) { + map.set(key, value as AnyConstructor); + } + } + return map; + }); + function getBuiltinType(name: string, value: object, recurser: Recurser): ValueInfo | undefined { + return isArray(value) + ? { name, kind: ValueKind.Array, inner: value.length && getValueInfo("element", first(value), recurser) || anyValue(name) } + : forEachEntry(builtins(), (builtin, builtinName): ValueInfo | undefined => + value instanceof builtin ? { kind: ValueKind.Const, name, typeName: builtinName } : undefined); + } + + function getPrototypeMembers(fn: AnyFunction, recurser: Recurser): ReadonlyArray { + const prototype = fn.prototype as unknown; + return typeof prototype !== "object" || prototype === null ? emptyArray : mapDefined(getEntriesOfObject(prototype as object), ({ key, value }) => + key === "constructor" ? undefined : getValueInfo(key, value, recurser)); + } + + const ignoredProperties: ReadonlySet = new Set(["arguments", "caller", "constructor", "eval", "super_"]); + const reservedFunctionProperties: ReadonlySet = new Set(Object.getOwnPropertyNames(noop)); + interface ObjectEntry { readonly key: string; readonly value: unknown; } + function getEntriesOfObject(obj: object): ReadonlyArray { + const seen = createMap(); + const entries: ObjectEntry[] = []; + let chain = obj; + while (!isNullOrUndefined(chain) && chain !== Object.prototype && chain !== Function.prototype) { + for (const key of Object.getOwnPropertyNames(chain)) { + if (!isJsPrivate(key) && + !ignoredProperties.has(key) && + (typeof obj !== "function" || !reservedFunctionProperties.has(key)) && + // Don't add property from a higher prototype if it already exists in a lower one + addToSeen(seen, key)) { + const value = safeGetPropertyOfObject(chain, key); + // Don't repeat "toString" that matches signature from Object.prototype + if (!(key === "toString" && typeof value === "function" && value.length === 0)) { + entries.push({ key, value }); + } + } + } + chain = Object.getPrototypeOf(chain); + } + return entries.sort((e1, e2) => compareStringsCaseSensitive(e1.key, e2.key)); + } + + function getFunctionLength(fn: AnyFunction): number { + return tryCast(safeGetPropertyOfObject(fn, "length"), isNumber) || 0; + } + + function safeGetPropertyOfObject(obj: object, key: string): unknown { + const desc = Object.getOwnPropertyDescriptor(obj, key); + return desc && desc.value; + } + + function isNullOrUndefined(value: unknown): value is null | undefined { + return value == null; // tslint:disable-line + } + + function anyValue(name: string, comment?: string): ValueInfo { + return { kind: ValueKind.Const, name, typeName: "any", comment }; + } + + export function isJsPrivate(name: string): boolean { + return name.startsWith("_"); + } + + function tryRequire(fileNameToRequire: string): unknown { + try { + return require(fileNameToRequire); + } + catch { + return undefined; + } + } +} diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 7fb308912c3fb..1471b4c2e7849 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -740,14 +740,23 @@ namespace ts { */ /* @internal */ export function resolveJavaScriptModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { - const { resolvedModule, failedLookupLocations } = - nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*jsOnly*/ true); + const { resolvedModule, failedLookupLocations } = tryResolveJavaScriptModuleWorker(moduleName, initialDir, host); if (!resolvedModule) { throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); } return resolvedModule.resolvedFileName; } + /* @internal */ + export function tryResolveJavaScriptModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string | undefined { + const { resolvedModule } = tryResolveJavaScriptModuleWorker(moduleName, initialDir, host); + return resolvedModule && resolvedModule.resolvedFileName; + } + + function tryResolveJavaScriptModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*jsOnly*/ true); + } + function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, jsOnly: boolean): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 20b97c1b7784a..d2d4c09430959 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -51,6 +51,7 @@ "resolutionCache.ts", "moduleSpecifiers.ts", "watch.ts", - "tsbuild.ts" + "tsbuild.ts", + "inspectValue.ts", ] } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 7ed3ae2e42904..75277aecd4b4d 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2493,9 +2493,7 @@ Actual: ${stringify(fullActual)}`); const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.emptyOptions); assert.deepEqual | undefined>(commands, expectedCommands); - assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files"); - this.applyChanges(changes); - this.verifyCurrentFileContent(newFileContent); + this.verifyNewContent({ newFileContent }, changes); } /** @@ -3380,6 +3378,19 @@ Actual: ${stringify(fullActual)}`); private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions): ReadonlyArray { return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences) || ts.emptyArray; } + + public generateTypes(examples: ReadonlyArray): void { + for (const { name = "example", value, output, outputBaseline } of examples) { + const actual = ts.generateTypesForModule(name, value, this.formatCodeSettings); + if (outputBaseline) { + if (actual === undefined) throw ts.Debug.fail(); + Harness.Baseline.runBaseline(ts.combinePaths("generateTypes", outputBaseline + ts.Extension.Dts), actual); + } + else { + assert.equal(actual, output, `generateTypes output for ${name} does not match`); + } + } + } } function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: ReadonlyArray): ts.TextRange { @@ -3433,7 +3444,7 @@ Actual: ${stringify(fullActual)}`); // Parse out the files and their metadata const testData = parseTestData(absoluteBasePath, content, absoluteFileName); const state = new TestState(absoluteBasePath, testType, testData); - const output = ts.transpileModule(content, { reportDiagnostics: true }); + const output = ts.transpileModule(content, { reportDiagnostics: true, compilerOptions: { target: ts.ScriptTarget.ES2015 } }); if (output.diagnostics!.length > 0) { throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`); } @@ -4503,6 +4514,18 @@ namespace FourSlashInterface { public noMoveToNewFile(): void { this.state.noMoveToNewFile(); } + + public generateTypes(...options: GenerateTypesOptions[]): void { + this.state.generateTypes(options); + } + } + + export interface GenerateTypesOptions { + readonly name?: string; + readonly value: unknown; + // Exactly one of these should be set: + readonly output?: string; + readonly outputBaseline?: string; } export class Edit { @@ -4890,7 +4913,7 @@ namespace FourSlashInterface { export interface VerifyCodeFixAllOptions { fixId: string; fixAllDescription: string; - newFileContent: string; + newFileContent: NewFileContent; commands: ReadonlyArray<{}>; } diff --git a/src/jsTyping/shared.ts b/src/jsTyping/shared.ts index 54b34193d063f..78e9fe051b7e7 100644 --- a/src/jsTyping/shared.ts +++ b/src/jsTyping/shared.ts @@ -4,6 +4,7 @@ namespace ts.server { export const ActionSet: ActionSet = "action::set"; export const ActionInvalidate: ActionInvalidate = "action::invalidate"; export const ActionPackageInstalled: ActionPackageInstalled = "action::packageInstalled"; + export const ActionValueInspected: ActionValueInspected = "action::valueInspected"; export const EventTypesRegistry: EventTypesRegistry = "event::typesRegistry"; export const EventBeginInstallTypes: EventBeginInstallTypes = "event::beginInstallTypes"; export const EventEndInstallTypes: EventEndInstallTypes = "event::endInstallTypes"; diff --git a/src/jsTyping/types.ts b/src/jsTyping/types.ts index 4e00c2eb6f5e4..408d30a65118a 100644 --- a/src/jsTyping/types.ts +++ b/src/jsTyping/types.ts @@ -2,6 +2,7 @@ declare namespace ts.server { export type ActionSet = "action::set"; export type ActionInvalidate = "action::invalidate"; export type ActionPackageInstalled = "action::packageInstalled"; + export type ActionValueInspected = "action::valueInspected"; export type EventTypesRegistry = "event::typesRegistry"; export type EventBeginInstallTypes = "event::beginInstallTypes"; export type EventEndInstallTypes = "event::endInstallTypes"; @@ -12,7 +13,7 @@ declare namespace ts.server { } export interface TypingInstallerResponse { - readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; + readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | ActionValueInspected | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; } export interface TypingInstallerRequestWithProjectName { @@ -20,7 +21,7 @@ declare namespace ts.server { } /* @internal */ - export type TypingInstallerRequestUnion = DiscoverTypings | CloseProject | TypesRegistryRequest | InstallPackageRequest; + export type TypingInstallerRequestUnion = DiscoverTypings | CloseProject | TypesRegistryRequest | InstallPackageRequest | InspectValueRequest; export interface DiscoverTypings extends TypingInstallerRequestWithProjectName { readonly fileNames: string[]; @@ -47,6 +48,12 @@ declare namespace ts.server { readonly projectRootPath: Path; } + /* @internal */ + export interface InspectValueRequest { + readonly kind: "inspectValue"; + readonly options: InspectValueOptions; + } + /* @internal */ export interface TypesRegistryResponse extends TypingInstallerResponse { readonly kind: EventTypesRegistry; @@ -59,6 +66,12 @@ declare namespace ts.server { readonly message: string; } + /* @internal */ + export interface InspectValueResponse { + readonly kind: ActionValueInspected; + readonly result: ValueInfo; + } + export interface InitializationFailedResponse extends TypingInstallerResponse { readonly kind: EventInitializationFailed; readonly message: string; @@ -106,5 +119,5 @@ declare namespace ts.server { } /* @internal */ - export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse; + export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InspectValueResponse | InstallTypes | InitializationFailedResponse; } diff --git a/src/server/project.ts b/src/server/project.ts index ff2c504a54a0a..885349d723d71 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -254,6 +254,11 @@ namespace ts.server { installPackage(options: InstallPackageOptions): Promise { return this.typingsCache.installPackage({ ...options, projectName: this.projectName, projectRootPath: this.toPath(this.currentDirectory) }); } + /* @internal */ + inspectValue(options: InspectValueOptions): Promise { + return this.typingsCache.inspectValue(options); + } + private get typingsCache(): TypingsCache { return this.projectService.typingsCache; } @@ -352,6 +357,10 @@ namespace ts.server { return this.projectService.host.readFile(fileName); } + writeFile(fileName: string, content: string): void { + return this.projectService.host.writeFile(fileName, content); + } + fileExists(file: string): boolean { // As an optimization, don't hit the disks for files we already know don't exist // (because we're watching for their creation). diff --git a/src/server/session.ts b/src/server/session.ts index 8d2fbe9e190a1..7da9e21e3f387 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1819,8 +1819,8 @@ namespace ts.server { private applyCodeActionCommand(args: protocol.ApplyCodeActionCommandRequestArgs): {} { const commands = args.command as CodeActionCommand | CodeActionCommand[]; // They should be sending back the command we sent them. for (const command of toArray(commands)) { - const { project } = this.getFileAndProject(command); - project.getLanguageService().applyCodeActionCommand(command).then( + const { file, project } = this.getFileAndProject(command); + project.getLanguageService().applyCodeActionCommand(command, this.getFormatOptions(file)).then( _result => { /* TODO: GH#20447 report success message? */ }, _error => { /* TODO: GH#20447 report errors */ }); } diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index 12b2303cfd359..64a117bbe258e 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -8,6 +8,8 @@ namespace ts.server { export interface ITypingsInstaller { isKnownTypesPackageName(name: string): boolean; installPackage(options: InstallPackageOptionsWithProject): Promise; + /* @internal */ + inspectValue(options: InspectValueOptions): Promise; enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray | undefined): void; attach(projectService: ProjectService): void; onProjectClosed(p: Project): void; @@ -18,6 +20,7 @@ namespace ts.server { isKnownTypesPackageName: returnFalse, // Should never be called because we never provide a types registry. installPackage: notImplemented, + inspectValue: notImplemented, enqueueInstallTypingsRequest: noop, attach: noop, onProjectClosed: noop, @@ -95,6 +98,10 @@ namespace ts.server { return this.installer.installPackage(options); } + inspectValue(options: InspectValueOptions): Promise { + return this.installer.inspectValue(options); + } + enqueueInstallTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray | undefined, forceRefresh: boolean) { const typeAcquisition = project.getTypeAcquisition(); diff --git a/src/services/codefixes/fixCannotFindModule.ts b/src/services/codefixes/fixCannotFindModule.ts index 68fa3a5c0305b..ae79fc9e8d767 100644 --- a/src/services/codefixes/fixCannotFindModule.ts +++ b/src/services/codefixes/fixCannotFindModule.ts @@ -1,6 +1,9 @@ /* @internal */ namespace ts.codefix { - const fixId = "fixCannotFindModule"; + const fixName = "fixCannotFindModule"; + const fixIdInstallTypesPackage = "installTypesPackage"; + const fixIdGenerateTypes = "generateTypes"; + const errorCodeCannotFindModule = Diagnostics.Cannot_find_module_0.code; const errorCodes = [ errorCodeCannotFindModule, @@ -10,26 +13,141 @@ namespace ts.codefix { errorCodes, getCodeActions: context => { const { host, sourceFile, span: { start } } = context; - const packageName = getTypesPackageNameToInstall(host, sourceFile, start, context.errorCode); - return packageName === undefined ? [] - : [createCodeFixAction(fixId, /*changes*/ [], [Diagnostics.Install_0, packageName], fixId, Diagnostics.Install_all_missing_types_packages, getCommand(sourceFile.fileName, packageName))]; + const packageName = tryGetImportedPackageName(sourceFile, start); + if (packageName === undefined) return undefined; + const typesPackageName = getTypesPackageNameToInstall(packageName, host, context.errorCode); + return typesPackageName === undefined + ? singleElementArray(tryGetGenerateTypesAction(context, packageName)) + : [createCodeFixAction(fixName, /*changes*/ [], [Diagnostics.Install_0, typesPackageName], fixIdInstallTypesPackage, Diagnostics.Install_all_missing_types_packages, getInstallCommand(sourceFile.fileName, typesPackageName))]; + }, + fixIds: [fixIdInstallTypesPackage, fixIdGenerateTypes], + getAllCodeActions: context => { + let savedTypesDir: string | null | undefined = null; // tslint:disable-line no-null-keyword + return codeFixAll(context, errorCodes, (changes, diag, commands) => { + const packageName = tryGetImportedPackageName(diag.file, diag.start); + if (packageName === undefined) return undefined; + switch (context.fixId) { + case fixIdInstallTypesPackage: { + const pkg = getTypesPackageNameToInstall(packageName, context.host, diag.code); + if (pkg) { + commands.push(getInstallCommand(diag.file.fileName, pkg)); + } + break; + } + case fixIdGenerateTypes: { + const typesDir = savedTypesDir !== null ? savedTypesDir : savedTypesDir = getOrCreateTypesDirectory(changes, context); + const command = typesDir === undefined ? undefined : tryGenerateTypes(typesDir, packageName, context); + if (command) commands.push(command); + break; + } + default: + Debug.fail(`Bad fixId: ${context.fixId}`); + } + }); }, - fixIds: [fixId], - getAllCodeActions: context => codeFixAll(context, errorCodes, (_, diag, commands) => { - const pkg = getTypesPackageNameToInstall(context.host, diag.file, diag.start, diag.code); - if (pkg) { - commands.push(getCommand(diag.file.fileName, pkg)); - } - }), }); - function getCommand(fileName: string, packageName: string): InstallPackageAction { + function tryGetGenerateTypesAction(context: CodeFixContextBase, packageName: string): CodeFixAction | undefined { + let command: GenerateTypesAction | undefined; + const changes = textChanges.ChangeTracker.with(context, t => { + const typesDir = getOrCreateTypesDirectory(t, context); + command = typesDir === undefined ? undefined : tryGenerateTypes(typesDir, packageName, context); + }); + return command && createCodeFixAction(fixName, changes, [Diagnostics.Generate_types_for_0, packageName], fixIdGenerateTypes, Diagnostics.Generate_types_for_all_packages_without_types, command); + } + + function tryGenerateTypes(typesDir: string, packageName: string, context: CodeFixContextBase): GenerateTypesAction | undefined { + const file = context.sourceFile.fileName; + const fileToGenerateTypesFor = tryResolveJavaScriptModule(packageName, getDirectoryPath(file), context.host as ModuleResolutionHost); // TODO: GH#18217 + if (fileToGenerateTypesFor === undefined) return undefined; + + const outputFileName = resolvePath(getDirectoryPath(context.program.getCompilerOptions().configFile!.fileName), typesDir, packageName + ".d.ts"); + if (context.host.fileExists!(outputFileName)) return undefined; + return { type: "generate types", file, fileToGenerateTypesFor, outputFileName }; + } + + // If no types directory exists yet, adds it to tsconfig.json + function getOrCreateTypesDirectory(changes: textChanges.ChangeTracker, context: CodeFixContextBase): string | undefined { + const { configFile } = context.program.getCompilerOptions(); + if (!configFile) return undefined; + + const tsconfigObjectLiteral = getTsConfigObjectLiteralExpression(configFile); + if (!tsconfigObjectLiteral) return undefined; + + const compilerOptionsProperty = findProperty(tsconfigObjectLiteral, "compilerOptions"); + if (!compilerOptionsProperty) { + const newCompilerOptions = createObjectLiteral([makeDefaultBaseUrl(), makeDefaultPaths()]); + changes.insertNodeAtObjectStart(configFile, tsconfigObjectLiteral, createJsonPropertyAssignment("compilerOptions", newCompilerOptions)); + return defaultTypesDirectoryName; + } + + const compilerOptions = compilerOptionsProperty.initializer; + if (!isObjectLiteralExpression(compilerOptions)) return defaultTypesDirectoryName; + + const baseUrl = getOrAddBaseUrl(changes, configFile, compilerOptions); + const typesDirectoryFromPathMapping = getOrAddPathMapping(changes, configFile, compilerOptions); + return combinePaths(baseUrl, typesDirectoryFromPathMapping); + } + + const defaultBaseUrl = "."; + function makeDefaultBaseUrl(): PropertyAssignment { + return createJsonPropertyAssignment("baseUrl", createStringLiteral(defaultBaseUrl)); + } + function getOrAddBaseUrl(changes: textChanges.ChangeTracker, tsconfig: TsConfigSourceFile, compilerOptions: ObjectLiteralExpression): string { + const baseUrlProp = findProperty(compilerOptions, "baseUrl"); + if (baseUrlProp) { + return isStringLiteral(baseUrlProp.initializer) ? baseUrlProp.initializer.text : defaultBaseUrl; + } + else { + changes.insertNodeAtObjectStart(tsconfig, compilerOptions, makeDefaultBaseUrl()); + return defaultBaseUrl; + } + } + + const defaultTypesDirectoryName = "types"; + function makeDefaultPathMapping(): PropertyAssignment { + return createJsonPropertyAssignment("*", createArrayLiteral([createStringLiteral(`${defaultTypesDirectoryName}/*`)])); + } + function makeDefaultPaths(): PropertyAssignment { + return createJsonPropertyAssignment("paths", createObjectLiteral([makeDefaultPathMapping()])); + } + function getOrAddPathMapping(changes: textChanges.ChangeTracker, tsconfig: TsConfigSourceFile, compilerOptions: ObjectLiteralExpression) { + const paths = findProperty(compilerOptions, "paths"); + if (!paths || !isObjectLiteralExpression(paths.initializer)) { + changes.insertNodeAtObjectStart(tsconfig, compilerOptions, makeDefaultPaths()); + return defaultTypesDirectoryName; + } + + // Look for an existing path mapping. Should look like `"*": "foo/*"`. + const existing = firstDefined(paths.initializer.properties, prop => + isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "*" && isArrayLiteralExpression(prop.initializer) + ? firstDefined(prop.initializer.elements, value => isStringLiteral(value) ? tryRemoveSuffix(value.text, "/*") : undefined) + : undefined); + if (existing) return existing; + + changes.insertNodeAtObjectStart(tsconfig, paths.initializer, makeDefaultPathMapping()); + return defaultTypesDirectoryName; + } + + function createJsonPropertyAssignment(name: string, initializer: Expression) { + return createPropertyAssignment(createStringLiteral(name), initializer); + } + + function findProperty(obj: ObjectLiteralExpression, name: string): PropertyAssignment | undefined { + return find(obj.properties, (p): p is PropertyAssignment => isPropertyAssignment(p) && !!p.name && isStringLiteral(p.name) && p.name.text === name); + } + + function getInstallCommand(fileName: string, packageName: string): InstallPackageAction { return { type: "install package", file: fileName, packageName }; } - function getTypesPackageNameToInstall(host: LanguageServiceHost, sourceFile: SourceFile, pos: number, diagCode: number): string | undefined { + function tryGetImportedPackageName(sourceFile: SourceFile, pos: number): string | undefined { const moduleName = cast(getTokenAtPosition(sourceFile, pos), isStringLiteral).text; const { packageName } = getPackageName(moduleName); + return isExternalModuleNameRelative(packageName) ? undefined : packageName; + } + + function getTypesPackageNameToInstall(packageName: string, host: LanguageServiceHost, diagCode: number): string | undefined { return diagCode === errorCodeCannotFindModule ? (JsTyping.nodeCoreModules.has(packageName) ? "@types/node" : undefined) : (host.isKnownTypesPackageName!(packageName) ? getTypesPackageName(packageName) : undefined); // TODO: GH#18217 diff --git a/src/services/codefixes/generateTypes.ts b/src/services/codefixes/generateTypes.ts new file mode 100644 index 0000000000000..6af72da562d44 --- /dev/null +++ b/src/services/codefixes/generateTypes.ts @@ -0,0 +1,227 @@ +/* @internal */ +namespace ts { + export function generateTypesForModule(name: string, moduleValue: unknown, formatSettings: FormatCodeSettings): string { + return valueInfoToDeclarationFileText(inspectValue(name, moduleValue), formatSettings); + } + + export function valueInfoToDeclarationFileText(valueInfo: ValueInfo, formatSettings: FormatCodeSettings): string { + return textChanges.getNewFileText(toStatements(valueInfo, OutputKind.ExportEquals), ScriptKind.TS, "\n", formatting.getFormatContext(formatSettings)); + } + + const enum OutputKind { ExportEquals, NamedExport, NamespaceMember } + function toNamespaceMemberStatements(info: ValueInfo): ReadonlyArray { + return toStatements(info, OutputKind.NamespaceMember); + } + function toStatements(info: ValueInfo, kind: OutputKind): ReadonlyArray { + const isDefault = info.name === InternalSymbolName.Default; + const name = isDefault ? "_default" : info.name; + if (!isValidIdentifier(name) || isDefault && kind !== OutputKind.NamedExport) return emptyArray; + + const modifiers = isDefault && info.kind === ValueKind.FunctionOrClass ? [createModifier(SyntaxKind.ExportKeyword), createModifier(SyntaxKind.DefaultKeyword)] + : kind === OutputKind.ExportEquals ? [createModifier(SyntaxKind.DeclareKeyword)] + : kind === OutputKind.NamedExport ? [createModifier(SyntaxKind.ExportKeyword)] + : undefined; + const exportEquals = () => kind === OutputKind.ExportEquals ? [exportEqualsOrDefault(info.name, /*isExportEquals*/ true)] : emptyArray; + const exportDefault = () => isDefault ? [exportEqualsOrDefault("_default", /*isExportEquals*/ false)] : emptyArray; + + switch (info.kind) { + case ValueKind.FunctionOrClass: + return [...exportEquals(), ...functionOrClassToStatements(modifiers, name, info)]; + case ValueKind.Object: + const { members } = info; + if (kind === OutputKind.ExportEquals) { + return flatMap(members, v => toStatements(v, OutputKind.NamedExport)); + } + if (members.some(m => m.kind === ValueKind.FunctionOrClass)) { + // If some member is a function, use a namespace so it gets a FunctionDeclaration or ClassDeclaration. + return [...exportDefault(), createNamespace(modifiers, name, flatMap(members, toNamespaceMemberStatements))]; + } + // falls through + case ValueKind.Const: + case ValueKind.Array: { + const comment = info.kind === ValueKind.Const ? info.comment : undefined; + const constVar = createVariableStatement(modifiers, createVariableDeclarationList([createVariableDeclaration(name, toType(info))], NodeFlags.Const)); + return [...exportEquals(), ...exportDefault(), addComment(constVar, comment)]; + } + default: + return Debug.assertNever(info); + } + } + function exportEqualsOrDefault(name: string, isExportEquals: boolean): ExportAssignment { + return createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, isExportEquals, createIdentifier(name)); + } + + function functionOrClassToStatements(modifiers: Modifiers, name: string, { source, prototypeMembers, namespaceMembers }: ValueInfoFunctionOrClass): ReadonlyArray { + const fnAst = parseClassOrFunctionBody(source); + const { parameters, returnType } = fnAst === undefined ? { parameters: emptyArray, returnType: anyType() } : getParametersAndReturnType(fnAst); + const instanceProperties = typeof fnAst === "object" ? getConstructorFunctionInstanceProperties(fnAst) : emptyArray; + + const classStaticMembers: ClassElement[] | undefined = + instanceProperties.length !== 0 || prototypeMembers.length !== 0 || fnAst === undefined || typeof fnAst !== "number" && fnAst.kind === SyntaxKind.Constructor ? [] : undefined; + + const namespaceStatements = flatMap(namespaceMembers, info => { + if (!isValidIdentifier(info.name)) return undefined; + if (classStaticMembers) { + switch (info.kind) { + case ValueKind.Object: + if (info.members.some(m => m.kind === ValueKind.FunctionOrClass)) { + break; + } + // falls through + case ValueKind.Array: + case ValueKind.Const: + classStaticMembers.push( + addComment( + createProperty(/*decorators*/ undefined, [createModifier(SyntaxKind.StaticKeyword)], info.name, /*questionOrExclamationToken*/ undefined, toType(info), /*initializer*/ undefined), + info.kind === ValueKind.Const ? info.comment : undefined)); + return undefined; + case ValueKind.FunctionOrClass: + if (!info.namespaceMembers.length) { // Else, can't merge a static method with a namespace. Must make it a function on the namespace. + const sig = tryGetMethod(info, [createModifier(SyntaxKind.StaticKeyword)]); + if (sig) { + classStaticMembers.push(sig); + return undefined; + } + } + } + } + return toStatements(info, OutputKind.NamespaceMember); + }); + + const decl = classStaticMembers + ? createClassDeclaration( + /*decorators*/ undefined, + modifiers, + name, + /*typeParameters*/ undefined, + /*heritageClauses*/ undefined, + [ + ...classStaticMembers, + ...(parameters.length ? [createConstructor(/*decorators*/ undefined, /*modifiers*/ undefined, parameters, /*body*/ undefined)] : emptyArray), + ...instanceProperties, + // ignore non-functions on the prototype + ...mapDefined(prototypeMembers, info => info.kind === ValueKind.FunctionOrClass ? tryGetMethod(info) : undefined), + ]) + : createFunctionDeclaration(/*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, name, /*typeParameters*/ undefined, parameters, returnType, /*body*/ undefined); + return [decl, ...(namespaceStatements.length === 0 ? emptyArray : [createNamespace(modifiers && modifiers.map(m => getSynthesizedDeepClone(m)), name, namespaceStatements)])]; + } + + function tryGetMethod({ name, source }: ValueInfoFunctionOrClass, modifiers?: Modifiers): MethodDeclaration | undefined { + if (!isValidIdentifier(name)) return undefined; + const fnAst = parseClassOrFunctionBody(source); + if (fnAst === undefined || (typeof fnAst !== "number" && fnAst.kind === SyntaxKind.Constructor)) return undefined; + const sig = getParametersAndReturnType(fnAst); + return sig && createMethod( + /*decorators*/ undefined, + modifiers, + /*asteriskToken*/ undefined, + name, + /*questionToken*/ undefined, + /*typeParameters*/ undefined, + sig.parameters, + sig.returnType, + /*body*/ undefined); + } + + function toType(info: ValueInfo): TypeNode { + switch (info.kind) { + case ValueKind.Const: + return createTypeReferenceNode(info.typeName, /*typeArguments*/ undefined); + case ValueKind.Array: + return createArrayTypeNode(toType(info.inner)); + case ValueKind.FunctionOrClass: + return createTypeReferenceNode("Function", /*typeArguments*/ undefined); // Normally we create a FunctionDeclaration, but this can happen for a function in an array. + case ValueKind.Object: + return createTypeLiteralNode(info.members.map(m => createPropertySignature(/*modifiers*/ undefined, m.name, /*questionToken*/ undefined, toType(m), /*initializer*/ undefined))); + default: + return Debug.assertNever(info); + } + } + + // Parses assignments to "this.x" in the constructor into class property declarations + function getConstructorFunctionInstanceProperties(fnAst: FunctionOrConstructorNode): ReadonlyArray { + const members: PropertyDeclaration[] = []; + forEachOwnNodeOfFunction(fnAst, node => { + if (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true) && + isPropertyAccessExpression(node.left) && node.left.expression.kind === SyntaxKind.ThisKeyword) { + const name = node.left.name.text; + if (!isJsPrivate(name)) members.push(createProperty(/*decorators*/ undefined, /*modifiers*/ undefined, name, /*questionOrExclamationToken*/ undefined, anyType(), /*initializer*/ undefined)); + } + }); + return members; + } + + interface ParametersAndReturnType { readonly parameters: ReadonlyArray; readonly returnType: TypeNode; } + function getParametersAndReturnType(fnAst: FunctionOrConstructor): ParametersAndReturnType { + if (typeof fnAst === "number") { + return { parameters: fill(fnAst, i => makeParameter(`p${i}`, anyType())), returnType: anyType() }; + } + let usedArguments = false, hasReturn = false; + forEachOwnNodeOfFunction(fnAst, node => { + usedArguments = usedArguments || isIdentifier(node) && node.text === "arguments"; + hasReturn = hasReturn || isReturnStatement(node) && !!node.expression && node.expression.kind !== SyntaxKind.VoidExpression; + }); + const parameters = [ + ...fnAst.parameters.map(p => makeParameter(`${p.name.getText()}`, inferParameterType(fnAst, p))), + ...(usedArguments ? [makeRestParameter()] : emptyArray), + ]; + return { parameters, returnType: hasReturn ? anyType() : createKeywordTypeNode(SyntaxKind.VoidKeyword) }; + } + function makeParameter(name: string, type: TypeNode): ParameterDeclaration { + return createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, name, /*questionToken*/ undefined, type); + } + function makeRestParameter(): ParameterDeclaration { + return createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, createToken(SyntaxKind.DotDotDotToken), "args", /*questionToken*/ undefined, createArrayTypeNode(anyType())); + } + + type FunctionOrConstructorNode = FunctionExpression | ArrowFunction | ConstructorDeclaration | MethodDeclaration; + type FunctionOrConstructor = FunctionOrConstructorNode | number; // number is for native function + /** Returns 'undefined' for class with no declared constructor */ + function parseClassOrFunctionBody(source: string | number): FunctionOrConstructor | undefined { + if (typeof source === "number") return source; + const classOrFunction = tryCast(parseExpression(source), (node): node is FunctionExpression | ArrowFunction | ClassExpression => isFunctionExpression(node) || isArrowFunction(node) || isClassExpression(node)); + return classOrFunction + ? isClassExpression(classOrFunction) ? find(classOrFunction.members, isConstructorDeclaration) : classOrFunction + // If that didn't parse, it's a method `m() {}`. Parse again inside of an object literal. + : cast(first(cast(parseExpression(`{ ${source} }`), isObjectLiteralExpression).properties), isMethodDeclaration); + } + + function parseExpression(expr: string): Expression { + const text = `const _ = ${expr}`; + const srcFile = createSourceFile("test.ts", text, ScriptTarget.Latest, /*setParentNodes*/ true); + return first(cast(first(srcFile.statements), isVariableStatement).declarationList.declarations).initializer!; + } + + function inferParameterType(_fn: FunctionOrConstructor, _param: ParameterDeclaration): TypeNode { + // TODO: Inspect function body for clues (see inferFromUsage.ts) + return anyType(); + } + + // Descends through all nodes in a function, but not in nested functions. + function forEachOwnNodeOfFunction(fnAst: FunctionOrConstructorNode, cb: (node: Node) => void) { + fnAst.body!.forEachChild(function recur(node) { + cb(node); + if (!isFunctionLike(node)) node.forEachChild(recur); + }); + } + + function isValidIdentifier(name: string): boolean { + const keyword = stringToToken(name); + return !(keyword && isNonContextualKeyword(keyword)) && isIdentifierText(name, ScriptTarget.ESNext); + } + + type Modifiers = ReadonlyArray | undefined; + + function addComment(node: T, comment: string | undefined): T { + if (comment !== undefined) addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, comment); + return node; + } + + function anyType(): KeywordTypeNode { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + + function createNamespace(modifiers: Modifiers, name: string, statements: ReadonlyArray): NamespaceDeclaration { + return createModuleDeclaration(/*decorators*/ undefined, modifiers, createIdentifier(name), createModuleBlock(statements), NodeFlags.Namespace) as NamespaceDeclaration; + } +} diff --git a/src/services/services.ts b/src/services/services.ts index dcc321b00d58c..4ff0e469ab87c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1803,25 +1803,36 @@ namespace ts { return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences, sourceMapper); } - function applyCodeActionCommand(action: CodeActionCommand): Promise; - function applyCodeActionCommand(action: CodeActionCommand[]): Promise; - function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; + function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; + function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise; function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise; - function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrUndefined?: CodeActionCommand | CodeActionCommand[]): Promise { - const action = typeof fileName === "string" ? actionOrUndefined! : fileName as CodeActionCommand[]; - return isArray(action) ? Promise.all(action.map(applySingleCodeActionCommand)) : applySingleCodeActionCommand(action); + function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise { + const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[]; + const formatSettings = typeof fileName !== "string" ? actionOrFormatSettingsOrUndefined as FormatCodeSettings : undefined; + return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a, formatSettings))) : applySingleCodeActionCommand(action, formatSettings); } - function applySingleCodeActionCommand(action: CodeActionCommand): Promise { + function applySingleCodeActionCommand(action: CodeActionCommand, formatSettings: FormatCodeSettings | undefined): Promise { + const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName); switch (action.type) { case "install package": return host.installPackage - ? host.installPackage({ fileName: toPath(action.file, currentDirectory, getCanonicalFileName), packageName: action.packageName }) + ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName }) : Promise.reject("Host does not implement `installPackage`"); + case "generate types": { + const { fileToGenerateTypesFor, outputFileName } = action; + if (!host.inspectValue) return Promise.reject("Host does not implement `installPackage`"); + const valueInfoPromise = host.inspectValue({ fileNameToRequire: fileToGenerateTypesFor }); + return valueInfoPromise.then(valueInfo => { + const fullOut = getPath(outputFileName); + host.writeFile!(fullOut, valueInfoToDeclarationFileText(valueInfo, formatSettings || testFormatSettings)); // TODO: GH#18217 + return { successMessage: `Wrote types to '${fullOut}'` }; + }); + } default: - return Debug.fail(); - // TODO: Debug.assertNever(action); will only work if there is more than one type. + return Debug.assertNever(action); } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 51e60d08741f8..9a2fa12d84b16 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -211,8 +211,8 @@ namespace ts.textChanges { export class ChangeTracker { private readonly changes: Change[] = []; - private readonly newFiles: { readonly oldFile: SourceFile, readonly fileName: string, readonly statements: ReadonlyArray }[] = []; - private readonly classesWithNodesInsertedAtStart = createMap(); // Set implemented as Map + private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: ReadonlyArray }[] = []; + private readonly classesWithNodesInsertedAtStart = createMap<{ readonly node: ClassDeclaration | InterfaceDeclaration | ObjectLiteralExpression, readonly sourceFile: SourceFile }>(); // Set implemented as Map private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray }[] = []; public static fromContext(context: TextChangesContext): ChangeTracker { @@ -410,25 +410,33 @@ namespace ts.textChanges { } public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration, newElement: ClassElement): void { + this.insertNodeAtStartWorker(sourceFile, cls, newElement); + } + public insertNodeAtObjectStart(sourceFile: SourceFile, obj: ObjectLiteralExpression, newElement: ObjectLiteralElementLike): void { + this.insertNodeAtStartWorker(sourceFile, obj, newElement); + } + + private insertNodeAtStartWorker(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, newElement: ClassElement | ObjectLiteralElementLike): void { const clsStart = cls.getStart(sourceFile); const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(clsStart, sourceFile), clsStart, sourceFile, this.formatContext.options) + this.formatContext.options.indentSize!; - this.insertNodeAt(sourceFile, cls.members.pos, newElement, { indentation, ...this.getInsertNodeAtClassStartPrefixSuffix(sourceFile, cls) }); + this.insertNodeAt(sourceFile, getMembersOrProperties(cls).pos, newElement, { indentation, ...this.getInsertNodeAtStartPrefixSuffix(sourceFile, cls) }); } - private getInsertNodeAtClassStartPrefixSuffix(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration): { prefix: string, suffix: string } { - if (cls.members.length === 0) { - if (addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(cls), cls)) { + private getInsertNodeAtStartPrefixSuffix(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression): { prefix: string, suffix: string } { + const comma = isObjectLiteralExpression(cls) ? "," : ""; + if (getMembersOrProperties(cls).length === 0) { + if (addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(cls), { node: cls, sourceFile })) { // For `class C {\n}`, don't add the trailing "\n" - const shouldSuffix = (positionsAreOnSameLine as any)(...getClassBraceEnds(cls, sourceFile), sourceFile); // TODO: GH#4130 remove 'as any' - return { prefix: this.newLineCharacter, suffix: shouldSuffix ? this.newLineCharacter : "" }; + const shouldSuffix = (positionsAreOnSameLine as any)(...getClassOrObjectBraceEnds(cls, sourceFile), sourceFile); // TODO: GH#4130 remove 'as any' + return { prefix: this.newLineCharacter, suffix: comma + (shouldSuffix ? this.newLineCharacter : "") }; } else { - return { prefix: "", suffix: this.newLineCharacter }; + return { prefix: "", suffix: comma + this.newLineCharacter }; } } else { - return { prefix: this.newLineCharacter, suffix: "" }; + return { prefix: this.newLineCharacter, suffix: comma }; } } @@ -647,9 +655,8 @@ namespace ts.textChanges { } private finishClassesWithNodesInsertedAtStart(): void { - this.classesWithNodesInsertedAtStart.forEach(cls => { - const sourceFile = cls.getSourceFile(); - const [openBraceEnd, closeBraceEnd] = getClassBraceEnds(cls, sourceFile); + this.classesWithNodesInsertedAtStart.forEach(({ node, sourceFile }) => { + const [openBraceEnd, closeBraceEnd] = getClassOrObjectBraceEnds(node, sourceFile); // For `class C { }` remove the whitespace inside the braces. if (positionsAreOnSameLine(openBraceEnd, closeBraceEnd, sourceFile) && openBraceEnd !== closeBraceEnd - 1) { this.deleteRange(sourceFile, createTextRange(openBraceEnd, closeBraceEnd - 1)); @@ -698,7 +705,7 @@ namespace ts.textChanges { return changes; } - public createNewFile(oldFile: SourceFile, fileName: string, statements: ReadonlyArray) { + public createNewFile(oldFile: SourceFile | undefined, fileName: string, statements: ReadonlyArray) { this.newFiles.push({ oldFile, fileName, statements }); } } @@ -708,12 +715,19 @@ namespace ts.textChanges { return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); } - function getClassBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration, sourceFile: SourceFile): [number, number] { + function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number, number] { return [findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile)!.end, findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)!.end]; } + function getMembersOrProperties(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression): NodeArray { + return isObjectLiteralExpression(cls) ? cls.properties : cls.members; + } export type ValidateNonFormattedText = (node: Node, text: string) => void; + export function getNewFileText(statements: ReadonlyArray, scriptKind: ScriptKind, newLineCharacter: string, formatContext: formatting.FormatContext): string { + return changesToText.newFileChangesWorker(/*oldFile*/ undefined, scriptKind, statements, newLineCharacter, formatContext); + } + namespace changesToText { export function getTextChangesFromChanges(changes: ReadonlyArray, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): FileTextChanges[] { return group(changes, c => c.sourceFile.path).map(changesInFile => { @@ -732,13 +746,17 @@ namespace ts.textChanges { }); } - export function newFileChanges(oldFile: SourceFile, fileName: string, statements: ReadonlyArray, newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges { + export function newFileChanges(oldFile: SourceFile | undefined, fileName: string, statements: ReadonlyArray, newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges { + const text = newFileChangesWorker(oldFile, getScriptKindFromFileName(fileName), statements, newLineCharacter, formatContext); + return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true }; + } + + export function newFileChangesWorker(oldFile: SourceFile | undefined, scriptKind: ScriptKind, statements: ReadonlyArray, newLineCharacter: string, formatContext: formatting.FormatContext): string { // TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this const nonFormattedText = statements.map(s => getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter); - const sourceFile = createSourceFile(fileName, nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true); + const sourceFile = createSourceFile("any file name", nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true, scriptKind); const changes = formatting.formatDocument(sourceFile, formatContext); - const text = applyChanges(nonFormattedText, changes); - return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true }; + return applyChanges(nonFormattedText, changes); } function computeNewText(change: Change, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index fee6da0f8a317..6f718b43730f0 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -69,6 +69,7 @@ "codefixes/inferFromUsage.ts", "codefixes/fixInvalidImportSyntax.ts", "codefixes/fixStrictClassInitialization.ts", + "codefixes/generateTypes.ts", "codefixes/requireInTs.ts", "codefixes/useDefaultImport.ts", "codefixes/fixAddModuleReferTypeMissingTypeof.ts", @@ -82,6 +83,6 @@ "services.ts", "breakpoints.ts", "transform.ts", - "shims.ts" + "shims.ts", ] } diff --git a/src/services/types.ts b/src/services/types.ts index 51c98724c0957..7fca4b4802c05 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -231,8 +231,11 @@ namespace ts { isKnownTypesPackageName?(name: string): boolean; installPackage?(options: InstallPackageOptions): Promise; + /* @internal */ inspectValue?(options: InspectValueOptions): Promise; + writeFile?(fileName: string, content: string): void; } + export interface UserPreferences { readonly disableSuggestions?: boolean; readonly quotePreference?: "double" | "single"; @@ -339,9 +342,9 @@ namespace ts { getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray; getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences): CombinedCodeActions; - applyCodeActionCommand(action: CodeActionCommand): Promise; - applyCodeActionCommand(action: CodeActionCommand[]): Promise; - applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; + applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise; /** @deprecated `fileName` will be ignored */ @@ -534,12 +537,22 @@ namespace ts { // 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 type CodeActionCommand = InstallPackageAction | GenerateTypesAction; export interface InstallPackageAction { - /* @internal */ file: string; - /* @internal */ type: "install package"; - /* @internal */ packageName: string; + /* @internal */ readonly type: "install package"; + /* @internal */ readonly file: string; + /* @internal */ readonly packageName: string; + } + + export interface GenerateTypesAction extends GenerateTypesOptions { + /* @internal */ readonly type: "generate types"; + } + + export interface GenerateTypesOptions { + readonly file: string; // File that was importing fileToGenerateTypesFor; used for formatting options. + readonly fileToGenerateTypesFor: string; + readonly outputFileName: string; } /** @@ -725,6 +738,31 @@ namespace ts { indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; } + /* @internal */ + export const testFormatSettings: FormatCodeSettings = { + baseIndentSize: 0, + indentSize: 4, + tabSize: 4, + newLineCharacter: "\n", + convertTabsToSpaces: true, + indentStyle: IndentStyle.Smart, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterConstructor: false, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceAfterTypeAssertion: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + insertSpaceBeforeTypeAnnotation: false + }; + export interface DefinitionInfo extends DocumentSpan { kind: ScriptElementKind; name: string; diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index f39b8080ed234..c3917a11548e8 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -357,7 +357,7 @@ interface Array {}` cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, preferences: emptyOptions, host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatOptions) + formatContext: formatting.getFormatContext(testFormatSettings) }; const diagnostics = languageService.getSuggestionDiagnostics(f.path); diff --git a/src/testRunner/unittests/extractTestHelpers.ts b/src/testRunner/unittests/extractTestHelpers.ts index 6e46c1304ab6e..6a74a637e9277 100644 --- a/src/testRunner/unittests/extractTestHelpers.ts +++ b/src/testRunner/unittests/extractTestHelpers.ts @@ -64,27 +64,6 @@ namespace ts { } export const newLineCharacter = "\n"; - export const testFormatOptions: FormatCodeSettings = { - indentSize: 4, - tabSize: 4, - newLineCharacter, - convertTabsToSpaces: true, - indentStyle: IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - }; export const notImplementedHost: LanguageServiceHost = { getCompilationSettings: notImplemented, @@ -123,7 +102,7 @@ namespace ts { startPosition: selectionRange.pos, endPosition: selectionRange.end, host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatOptions), + formatContext: formatting.getFormatContext(testFormatSettings), preferences: emptyOptions, }; const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange)); @@ -185,7 +164,7 @@ namespace ts { startPosition: selectionRange.pos, endPosition: selectionRange.end, host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatOptions), + formatContext: formatting.getFormatContext(testFormatSettings), preferences: emptyOptions, }; const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange)); diff --git a/src/testRunner/unittests/organizeImports.ts b/src/testRunner/unittests/organizeImports.ts index b4f54afa21b26..355292c6d897c 100644 --- a/src/testRunner/unittests/organizeImports.ts +++ b/src/testRunner/unittests/organizeImports.ts @@ -270,7 +270,7 @@ export const Other = 1; content: "function F() { }", }; const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatOptions, emptyOptions); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); assert.isEmpty(changes); }); @@ -741,7 +741,7 @@ export * from "lib"; function runBaseline(baselinePath: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { const { path: testPath, content: testContent } = testFile; const languageService = makeLanguageService(testFile, ...otherFiles); - const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatOptions, emptyOptions); + const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatSettings, emptyOptions); assert.equal(changes.length, 1); assert.equal(changes[0].fileName, testPath); diff --git a/src/testRunner/unittests/textChanges.ts b/src/testRunner/unittests/textChanges.ts index 699f668b5524e..164073207b3f4 100644 --- a/src/testRunner/unittests/textChanges.ts +++ b/src/testRunner/unittests/textChanges.ts @@ -20,7 +20,7 @@ namespace ts { const newLineCharacter = getNewLineCharacter(printerOptions); function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): formatting.FormatContext { - return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatOptions, placeOpenBraceOnNewLineForFunctions: true } : testFormatOptions); + return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : testFormatSettings); } // validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed. diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index b99a2d9fe999e..59c5b13d5c656 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -85,6 +85,7 @@ namespace ts.projectSystem { isKnownTypesPackageName = notImplemented; installPackage = notImplemented; + inspectValue = notImplemented; executePendingCommands() { const actionsToRun = this.postExecActions; @@ -8856,7 +8857,7 @@ export const x = 10;` Debug.assert(!!project.resolveModuleNames); - const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatOptions, emptyOptions); + const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatSettings, emptyOptions); assert.deepEqual>(edits, [{ fileName: "/user.ts", textChanges: [{ diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index 3e5986e940804..25ff26e421cc1 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -231,7 +231,8 @@ 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 } | undefined; + private packageInstalledPromise: { resolve(value: ApplyCodeActionCommandResult): void, reject(reason: unknown): void } | undefined; + private inspectValuePromise: { resolve(value: ValueInfo): void } | undefined; constructor( private readonly telemetryEnabled: boolean, @@ -261,14 +262,19 @@ namespace ts.server { } installPackage(options: InstallPackageOptionsWithProject): Promise { - const rq: InstallPackageRequest = { kind: "installPackage", ...options }; - this.send(rq); + this.send({ kind: "installPackage", ...options }); Debug.assert(this.packageInstalledPromise === undefined); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this.packageInstalledPromise = { resolve, reject }; }); } + inspectValue(options: InspectValueOptions): Promise { + this.send({ kind: "inspectValue", options }); + Debug.assert(this.inspectValuePromise === undefined); + return new Promise(resolve => { this.inspectValuePromise = { resolve }; }); + } + attach(projectService: ProjectService) { this.projectService = projectService; if (this.logger.hasLevel(LogLevel.requestTime)) { @@ -320,7 +326,7 @@ namespace ts.server { this.send({ projectName: p.getProjectName(), kind: "closeProject" }); } - private send(rq: TypingInstallerRequestUnion): void { + private send(rq: T): void { this.installer.send(rq); } @@ -353,7 +359,7 @@ namespace ts.server { } } - private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) { + private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | InspectValueResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) { if (this.logger.hasLevel(LogLevel.verbose)) { this.logger.info(`Received response:${stringifyIndented(response)}`); } @@ -378,6 +384,10 @@ namespace ts.server { this.event(response, "setTypings"); break; } + case ActionValueInspected: + this.inspectValuePromise!.resolve(response.result); + this.inspectValuePromise = undefined; + break; case EventInitializationFailed: { const body: protocol.TypesInstallerInitializationFailedEventBody = { diff --git a/src/typingsInstaller/nodeTypingsInstaller.ts b/src/typingsInstaller/nodeTypingsInstaller.ts index d2c7269f9bfbb..00ee8b4fa1923 100644 --- a/src/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/typingsInstaller/nodeTypingsInstaller.ts @@ -164,6 +164,11 @@ namespace ts.server.typingsInstaller { } break; } + case "inspectValue": { + const response: InspectValueResponse = { kind: ActionValueInspected, result: inspectModule(req.options.fileNameToRequire) }; + this.sendResponse(response); + break; + } default: Debug.assertNever(req); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5e992f3847a8b..d3231d1cf0a5c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4419,6 +4419,7 @@ declare namespace ts.server { type ActionSet = "action::set"; type ActionInvalidate = "action::invalidate"; type ActionPackageInstalled = "action::packageInstalled"; + type ActionValueInspected = "action::valueInspected"; type EventTypesRegistry = "event::typesRegistry"; type EventBeginInstallTypes = "event::beginInstallTypes"; type EventEndInstallTypes = "event::endInstallTypes"; @@ -4427,7 +4428,7 @@ declare namespace ts.server { " __sortedArrayBrand": any; } interface TypingInstallerResponse { - readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; + readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | ActionValueInspected | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; } interface TypingInstallerRequestWithProjectName { readonly projectName: string; @@ -4635,6 +4636,7 @@ declare namespace ts { getCustomTransformers?(): CustomTransformers | undefined; isKnownTypesPackageName?(name: string): boolean; installPackage?(options: InstallPackageOptions): Promise; + writeFile?(fileName: string, content: string): void; } interface UserPreferences { readonly disableSuggestions?: boolean; @@ -4700,9 +4702,9 @@ declare namespace ts { toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray; getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences): CombinedCodeActions; - applyCodeActionCommand(action: CodeActionCommand): Promise; - applyCodeActionCommand(action: CodeActionCommand[]): Promise; - applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; + applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise; /** @deprecated `fileName` will be ignored */ @@ -4864,9 +4866,16 @@ declare namespace ts { changes: ReadonlyArray; commands?: ReadonlyArray; } - type CodeActionCommand = InstallPackageAction; + type CodeActionCommand = InstallPackageAction | GenerateTypesAction; interface InstallPackageAction { } + interface GenerateTypesAction extends GenerateTypesOptions { + } + interface GenerateTypesOptions { + readonly file: string; + readonly fileToGenerateTypesFor: string; + readonly outputFileName: string; + } /** * A set of one or more available refactoring actions, grouped under a parent refactoring. */ @@ -8062,6 +8071,7 @@ declare namespace ts.server { useCaseSensitiveFileNames(): boolean; readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; readFile(fileName: string): string | undefined; + writeFile(fileName: string, content: string): void; fileExists(file: string): boolean; resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[]; getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 32761156558fd..8f37783250a7b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4419,6 +4419,7 @@ declare namespace ts.server { type ActionSet = "action::set"; type ActionInvalidate = "action::invalidate"; type ActionPackageInstalled = "action::packageInstalled"; + type ActionValueInspected = "action::valueInspected"; type EventTypesRegistry = "event::typesRegistry"; type EventBeginInstallTypes = "event::beginInstallTypes"; type EventEndInstallTypes = "event::endInstallTypes"; @@ -4427,7 +4428,7 @@ declare namespace ts.server { " __sortedArrayBrand": any; } interface TypingInstallerResponse { - readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; + readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | ActionValueInspected | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; } interface TypingInstallerRequestWithProjectName { readonly projectName: string; @@ -4635,6 +4636,7 @@ declare namespace ts { getCustomTransformers?(): CustomTransformers | undefined; isKnownTypesPackageName?(name: string): boolean; installPackage?(options: InstallPackageOptions): Promise; + writeFile?(fileName: string, content: string): void; } interface UserPreferences { readonly disableSuggestions?: boolean; @@ -4700,9 +4702,9 @@ declare namespace ts { toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray; getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences): CombinedCodeActions; - applyCodeActionCommand(action: CodeActionCommand): Promise; - applyCodeActionCommand(action: CodeActionCommand[]): Promise; - applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; + applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise; /** @deprecated `fileName` will be ignored */ @@ -4864,9 +4866,16 @@ declare namespace ts { changes: ReadonlyArray; commands?: ReadonlyArray; } - type CodeActionCommand = InstallPackageAction; + type CodeActionCommand = InstallPackageAction | GenerateTypesAction; interface InstallPackageAction { } + interface GenerateTypesAction extends GenerateTypesOptions { + } + interface GenerateTypesOptions { + readonly file: string; + readonly fileToGenerateTypesFor: string; + readonly outputFileName: string; + } /** * A set of one or more available refactoring actions, grouped under a parent refactoring. */ diff --git a/tests/baselines/reference/generateTypes/global.d.ts b/tests/baselines/reference/generateTypes/global.d.ts new file mode 100644 index 0000000000000..a49c80258aec2 --- /dev/null +++ b/tests/baselines/reference/generateTypes/global.d.ts @@ -0,0 +1,236 @@ +export class Array { + static from(p0: any): any; + static isArray(p0: any): any; + static of(): any; + concat(p0: any): any; + copyWithin(p0: any, p1: any): any; + entries(): any; + every(p0: any): any; + fill(p0: any): any; + filter(p0: any): any; + find(p0: any): any; + findIndex(p0: any): any; + forEach(p0: any): any; + includes(p0: any): any; + indexOf(p0: any): any; + join(p0: any): any; + keys(): any; + lastIndexOf(p0: any): any; + map(p0: any): any; + pop(): any; + push(p0: any): any; + reduce(p0: any): any; + reduceRight(p0: any): any; + reverse(): any; + shift(): any; + slice(p0: any, p1: any): any; + some(p0: any): any; + sort(p0: any): any; + splice(p0: any, p1: any): any; + toLocaleString(): any; + unshift(p0: any): any; +} +export class Boolean { + constructor(p0: any); + valueOf(): any; +} +export class Date { + static UTC(p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any): any; + static now(): any; + static parse(p0: any): any; + constructor(p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any); + getDate(): any; + getDay(): any; + getFullYear(): any; + getHours(): any; + getMilliseconds(): any; + getMinutes(): any; + getMonth(): any; + getSeconds(): any; + getTime(): any; + getTimezoneOffset(): any; + getUTCDate(): any; + getUTCDay(): any; + getUTCFullYear(): any; + getUTCHours(): any; + getUTCMilliseconds(): any; + getUTCMinutes(): any; + getUTCMonth(): any; + getUTCSeconds(): any; + getYear(): any; + setDate(p0: any): any; + setFullYear(p0: any, p1: any, p2: any): any; + setHours(p0: any, p1: any, p2: any, p3: any): any; + setMilliseconds(p0: any): any; + setMinutes(p0: any, p1: any, p2: any): any; + setMonth(p0: any, p1: any): any; + setSeconds(p0: any, p1: any): any; + setTime(p0: any): any; + setUTCDate(p0: any): any; + setUTCFullYear(p0: any, p1: any, p2: any): any; + setUTCHours(p0: any, p1: any, p2: any, p3: any): any; + setUTCMilliseconds(p0: any): any; + setUTCMinutes(p0: any, p1: any, p2: any): any; + setUTCMonth(p0: any, p1: any): any; + setUTCSeconds(p0: any, p1: any): any; + setYear(p0: any): any; + toDateString(): any; + toGMTString(): any; + toISOString(): any; + toJSON(p0: any): any; + toLocaleDateString(): any; + toLocaleString(): any; + toLocaleTimeString(): any; + toTimeString(): any; + toUTCString(): any; + valueOf(): any; +} +export namespace Math { + const E: number; + const LN10: number; + const LN2: number; + const LOG10E: number; + const LOG2E: number; + const PI: number; + const SQRT1_2: number; + const SQRT2: number; + function abs(p0: any): any; + function acos(p0: any): any; + function acosh(p0: any): any; + function asin(p0: any): any; + function asinh(p0: any): any; + function atan(p0: any): any; + function atan2(p0: any, p1: any): any; + function atanh(p0: any): any; + function cbrt(p0: any): any; + function ceil(p0: any): any; + function clz32(p0: any): any; + function cos(p0: any): any; + function cosh(p0: any): any; + function exp(p0: any): any; + function expm1(p0: any): any; + function floor(p0: any): any; + function fround(p0: any): any; + function hypot(p0: any, p1: any): any; + function imul(p0: any, p1: any): any; + function log(p0: any): any; + function log10(p0: any): any; + function log1p(p0: any): any; + function log2(p0: any): any; + function max(p0: any, p1: any): any; + function min(p0: any, p1: any): any; + function pow(p0: any, p1: any): any; + function random(): any; + function round(p0: any): any; + function sign(p0: any): any; + function sin(p0: any): any; + function sinh(p0: any): any; + function sqrt(p0: any): any; + function tan(p0: any): any; + function tanh(p0: any): any; + function trunc(p0: any): any; +} +export class Number { + static EPSILON: number; + static MAX_SAFE_INTEGER: number; + static MAX_VALUE: number; + static MIN_SAFE_INTEGER: number; + static MIN_VALUE: number; + static NEGATIVE_INFINITY: number; + static NaN: number; + static POSITIVE_INFINITY: number; + static isFinite(p0: any): any; + static isInteger(p0: any): any; + static isNaN(p0: any): any; + static isSafeInteger(p0: any): any; + static parseFloat(p0: any): any; + static parseInt(p0: any, p1: any): any; + constructor(p0: any); + toExponential(p0: any): any; + toFixed(p0: any): any; + toLocaleString(): any; + toPrecision(p0: any): any; + toString(p0: any): any; + valueOf(): any; +} +export class RegExp { + static $1: any; + static $2: any; + static $3: any; + static $4: any; + static $5: any; + static $6: any; + static $7: any; + static $8: any; + static $9: any; + static $_: any; + static input: any; + static lastMatch: any; + static lastParen: any; + static leftContext: any; + static rightContext: any; + constructor(p0: any, p1: any); + compile(p0: any, p1: any): any; + exec(p0: any): any; + test(p0: any): any; +} +export class String { + static fromCharCode(p0: any): any; + static fromCodePoint(p0: any): any; + static raw(p0: any): any; + anchor(p0: any): any; + big(): any; + blink(): any; + bold(): any; + charAt(p0: any): any; + charCodeAt(p0: any): any; + codePointAt(p0: any): any; + concat(p0: any): any; + endsWith(p0: any): any; + fixed(): any; + fontcolor(p0: any): any; + fontsize(p0: any): any; + includes(p0: any): any; + indexOf(p0: any): any; + italics(): any; + lastIndexOf(p0: any): any; + link(p0: any): any; + localeCompare(p0: any): any; + match(p0: any): any; + normalize(): any; + repeat(p0: any): any; + replace(p0: any, p1: any): any; + search(p0: any): any; + slice(p0: any, p1: any): any; + small(): any; + split(p0: any, p1: any): any; + startsWith(p0: any): any; + strike(): any; + sub(): any; + substr(p0: any, p1: any): any; + substring(p0: any, p1: any): any; + sup(): any; + toLocaleLowerCase(): any; + toLocaleUpperCase(): any; + toLowerCase(): any; + toUpperCase(): any; + trim(): any; + trimLeft(): any; + trimRight(): any; + valueOf(): any; +} +export class Symbol { + static hasInstance: symbol; + static isConcatSpreadable: symbol; + static iterator: symbol; + static keyFor(p0: any): any; + static match: symbol; + static replace: symbol; + static search: symbol; + static species: symbol; + static split: symbol; + static toPrimitive: symbol; + static toStringTag: symbol; + static unscopables: symbol; + valueOf(): any; +} \ No newline at end of file diff --git a/tests/baselines/reference/generateTypes/lodash.d.ts b/tests/baselines/reference/generateTypes/lodash.d.ts new file mode 100644 index 0000000000000..6354df710db84 --- /dev/null +++ b/tests/baselines/reference/generateTypes/lodash.d.ts @@ -0,0 +1,668 @@ +export = example; +declare class example { + static VERSION: string; + static add(value: any, other: any): any; + static after(n: any, func: any): any; + static ary(func: any, n: any, guard: any): any; + static assign(...args: any[]): any; + static assignIn(...args: any[]): any; + static assignInWith(...args: any[]): any; + static assignWith(...args: any[]): any; + static at(...args: any[]): any; + static attempt(...args: any[]): any; + static before(n: any, func: any): any; + static bindAll(...args: any[]): any; + static camelCase(string: any): any; + static capitalize(string: any): any; + static castArray(...args: any[]): any; + static ceil(number: any, precision: any): any; + static chain(value: any): any; + static chunk(array: any, size: any, guard: any): any; + static clamp(number: any, lower: any, upper: any): any; + static clone(value: any): any; + static cloneDeep(value: any): any; + static cloneDeepWith(value: any, customizer: any): any; + static cloneWith(value: any, customizer: any): any; + static compact(array: any): any; + static concat(...args: any[]): any; + static cond(pairs: any): any; + static conforms(source: any): any; + static conformsTo(object: any, source: any): any; + static constant(value: any): any; + static countBy(collection: any, iteratee: any): any; + static create(prototype: any, properties: any): any; + static debounce(func: any, wait: any, options: any): any; + static deburr(string: any): any; + static defaultTo(value: any, defaultValue: any): any; + static defaults(...args: any[]): any; + static defaultsDeep(...args: any[]): any; + static defer(...args: any[]): any; + static delay(...args: any[]): any; + static difference(...args: any[]): any; + static differenceBy(...args: any[]): any; + static differenceWith(...args: any[]): any; + static divide(value: any, other: any): any; + static drop(array: any, n: any, guard: any): any; + static dropRight(array: any, n: any, guard: any): any; + static dropRightWhile(array: any, predicate: any): any; + static dropWhile(array: any, predicate: any): any; + static each(collection: any, iteratee: any): any; + static eachRight(collection: any, iteratee: any): any; + static endsWith(string: any, target: any, position: any): any; + static entries(object: any): any; + static entriesIn(object: any): any; + static eq(value: any, other: any): any; + static escape(string: any): any; + static escapeRegExp(string: any): any; + static every(collection: any, predicate: any, guard: any): any; + static extend(...args: any[]): any; + static extendWith(...args: any[]): any; + static fill(array: any, value: any, start: any, end: any): any; + static filter(collection: any, predicate: any): any; + static find(collection: any, predicate: any, fromIndex: any): any; + static findIndex(array: any, predicate: any, fromIndex: any): any; + static findKey(object: any, predicate: any): any; + static findLast(collection: any, predicate: any, fromIndex: any): any; + static findLastIndex(array: any, predicate: any, fromIndex: any): any; + static findLastKey(object: any, predicate: any): any; + static first(array: any): any; + static flatMap(collection: any, iteratee: any): any; + static flatMapDeep(collection: any, iteratee: any): any; + static flatMapDepth(collection: any, iteratee: any, depth: any): any; + static flatten(array: any): any; + static flattenDeep(array: any): any; + static flattenDepth(array: any, depth: any): any; + static flip(func: any): any; + static floor(number: any, precision: any): any; + static flow(...args: any[]): any; + static flowRight(...args: any[]): any; + static forEach(collection: any, iteratee: any): any; + static forEachRight(collection: any, iteratee: any): any; + static forIn(object: any, iteratee: any): any; + static forInRight(object: any, iteratee: any): any; + static forOwn(object: any, iteratee: any): any; + static forOwnRight(object: any, iteratee: any): any; + static fromPairs(pairs: any): any; + static functions(object: any): any; + static functionsIn(object: any): any; + static get(object: any, path: any, defaultValue: any): any; + static groupBy(collection: any, iteratee: any): any; + static gt(value: any, other: any): any; + static gte(value: any, other: any): any; + static has(object: any, path: any): any; + static hasIn(object: any, path: any): any; + static head(array: any): any; + static identity(value: any): any; + static inRange(number: any, start: any, end: any): any; + static includes(collection: any, value: any, fromIndex: any, guard: any): any; + static indexOf(array: any, value: any, fromIndex: any): any; + static initial(array: any): any; + static intersection(...args: any[]): any; + static intersectionBy(...args: any[]): any; + static intersectionWith(...args: any[]): any; + static invert(object: any, iteratee: any): any; + static invertBy(object: any, iteratee: any): any; + static invoke(...args: any[]): any; + static invokeMap(...args: any[]): any; + static isArguments(value: any): any; + static isArray(p0: any): any; + static isArrayBuffer(value: any): any; + static isArrayLike(value: any): any; + static isArrayLikeObject(value: any): any; + static isBoolean(value: any): any; + static isBuffer(b: any): any; + static isDate(value: any): any; + static isElement(value: any): any; + static isEmpty(value: any): any; + static isEqual(value: any, other: any): any; + static isEqualWith(value: any, other: any, customizer: any): any; + static isError(value: any): any; + static isFinite(value: any): any; + static isFunction(value: any): any; + static isInteger(value: any): any; + static isLength(value: any): any; + static isMap(value: any): any; + static isMatch(object: any, source: any): any; + static isMatchWith(object: any, source: any, customizer: any): any; + static isNaN(value: any): any; + static isNative(value: any): any; + static isNil(value: any): any; + static isNull(value: any): any; + static isNumber(value: any): any; + static isObject(value: any): any; + static isObjectLike(value: any): any; + static isPlainObject(value: any): any; + static isRegExp(value: any): any; + static isSafeInteger(value: any): any; + static isSet(value: any): any; + static isString(value: any): any; + static isSymbol(value: any): any; + static isTypedArray(value: any): any; + static isUndefined(value: any): any; + static isWeakMap(value: any): any; + static isWeakSet(value: any): any; + static iteratee(func: any): any; + static join(array: any, separator: any): any; + static kebabCase(string: any): any; + static keyBy(collection: any, iteratee: any): any; + static keys(object: any): any; + static keysIn(object: any): any; + static last(array: any): any; + static lastIndexOf(array: any, value: any, fromIndex: any): any; + static lowerCase(string: any): any; + static lowerFirst(string: any): any; + static lt(value: any, other: any): any; + static lte(value: any, other: any): any; + static map(collection: any, iteratee: any): any; + static mapKeys(object: any, iteratee: any): any; + static mapValues(object: any, iteratee: any): any; + static matches(source: any): any; + static matchesProperty(path: any, srcValue: any): any; + static max(array: any): any; + static maxBy(array: any, iteratee: any): any; + static mean(array: any): any; + static meanBy(array: any, iteratee: any): any; + static merge(...args: any[]): any; + static mergeWith(...args: any[]): any; + static method(...args: any[]): any; + static methodOf(...args: any[]): any; + static min(array: any): any; + static minBy(array: any, iteratee: any): any; + static mixin(object: any, source: any, options: any): any; + static multiply(value: any, other: any): any; + static negate(predicate: any): any; + static noConflict(): any; + static noop(): void; + static now(): any; + static nth(array: any, n: any): any; + static nthArg(n: any): any; + static omit(...args: any[]): any; + static omitBy(object: any, predicate: any): any; + static once(func: any): any; + static orderBy(collection: any, iteratees: any, orders: any, guard: any): any; + static over(...args: any[]): any; + static overArgs(...args: any[]): any; + static overEvery(...args: any[]): any; + static overSome(...args: any[]): any; + static pad(string: any, length: any, chars: any): any; + static padEnd(string: any, length: any, chars: any): any; + static padStart(string: any, length: any, chars: any): any; + static parseInt(string: any, radix: any, guard: any): any; + static partition(collection: any, iteratee: any): any; + static pick(...args: any[]): any; + static pickBy(object: any, predicate: any): any; + static property(path: any): any; + static propertyOf(object: any): any; + static pull(...args: any[]): any; + static pullAll(array: any, values: any): any; + static pullAllBy(array: any, values: any, iteratee: any): any; + static pullAllWith(array: any, values: any, comparator: any): any; + static pullAt(...args: any[]): any; + static random(lower: any, upper: any, floating: any): any; + static range(start: any, end: any, step: any): any; + static rangeRight(start: any, end: any, step: any): any; + static rearg(...args: any[]): any; + static reduce(collection: any, iteratee: any, accumulator: any, ...args: any[]): any; + static reduceRight(collection: any, iteratee: any, accumulator: any, ...args: any[]): any; + static reject(collection: any, predicate: any): any; + static remove(array: any, predicate: any): any; + static repeat(string: any, n: any, guard: any): any; + static replace(...args: any[]): any; + static rest(func: any, start: any): any; + static result(object: any, path: any, defaultValue: any): any; + static reverse(array: any): any; + static round(number: any, precision: any): any; + static runInContext(context: any): any; + static sample(collection: any): any; + static sampleSize(collection: any, n: any, guard: any): any; + static set(object: any, path: any, value: any): any; + static setWith(object: any, path: any, value: any, customizer: any): any; + static shuffle(collection: any): any; + static size(collection: any): any; + static slice(array: any, start: any, end: any): any; + static snakeCase(string: any): any; + static some(collection: any, predicate: any, guard: any): any; + static sortBy(...args: any[]): any; + static sortedIndex(array: any, value: any): any; + static sortedIndexBy(array: any, value: any, iteratee: any): any; + static sortedIndexOf(array: any, value: any): any; + static sortedLastIndex(array: any, value: any): any; + static sortedLastIndexBy(array: any, value: any, iteratee: any): any; + static sortedLastIndexOf(array: any, value: any): any; + static sortedUniq(array: any): any; + static sortedUniqBy(array: any, iteratee: any): any; + static split(string: any, separator: any, limit: any): any; + static spread(func: any, start: any): any; + static startCase(string: any): any; + static startsWith(string: any, target: any, position: any): any; + static stubArray(): any; + static stubFalse(): any; + static stubObject(): any; + static stubString(): any; + static stubTrue(): any; + static subtract(value: any, other: any): any; + static sum(array: any): any; + static sumBy(array: any, iteratee: any): any; + static tail(array: any): any; + static take(array: any, n: any, guard: any): any; + static takeRight(array: any, n: any, guard: any): any; + static takeRightWhile(array: any, predicate: any): any; + static takeWhile(array: any, predicate: any): any; + static tap(value: any, interceptor: any): any; + static template(string: any, options: any, guard: any): any; + static templateSettings: { + escape: RegExp; + evaluate: RegExp; + imports: {}; + interpolate: RegExp; + variable: string; + }; + static throttle(func: any, wait: any, options: any): any; + static thru(value: any, interceptor: any): any; + static times(n: any, iteratee: any): any; + static toArray(value: any): any; + static toFinite(value: any): any; + static toInteger(value: any): any; + static toLength(value: any): any; + static toLower(value: any): any; + static toNumber(value: any): any; + static toPairs(object: any): any; + static toPairsIn(object: any): any; + static toPath(value: any): any; + static toPlainObject(value: any): any; + static toSafeInteger(value: any): any; + static toString(value: any): any; + static toUpper(value: any): any; + static transform(object: any, iteratee: any, accumulator: any): any; + static trim(string: any, chars: any, guard: any): any; + static trimEnd(string: any, chars: any, guard: any): any; + static trimStart(string: any, chars: any, guard: any): any; + static truncate(string: any, options: any): any; + static unary(func: any): any; + static unescape(string: any): any; + static union(...args: any[]): any; + static unionBy(...args: any[]): any; + static unionWith(...args: any[]): any; + static uniq(array: any): any; + static uniqBy(array: any, iteratee: any): any; + static uniqWith(array: any, comparator: any): any; + static uniqueId(prefix: any): any; + static unset(object: any, path: any): any; + static unzip(array: any): any; + static unzipWith(array: any, iteratee: any): any; + static update(object: any, path: any, updater: any): any; + static updateWith(object: any, path: any, updater: any, customizer: any): any; + static upperCase(string: any): any; + static upperFirst(string: any): any; + static values(object: any): any; + static valuesIn(object: any): any; + static without(...args: any[]): any; + static words(string: any, pattern: any, guard: any): any; + static wrap(value: any, wrapper: any): any; + static xor(...args: any[]): any; + static xorBy(...args: any[]): any; + static xorWith(...args: any[]): any; + static zip(...args: any[]): any; + static zipObject(props: any, values: any): any; + static zipObjectDeep(props: any, values: any): any; + static zipWith(...args: any[]): any; + constructor(value: any); + add(...args: any[]): any; + after(...args: any[]): any; + ary(...args: any[]): any; + assign(...args: any[]): any; + assignIn(...args: any[]): any; + assignInWith(...args: any[]): any; + assignWith(...args: any[]): any; + at(...args: any[]): any; + attempt(...args: any[]): any; + before(...args: any[]): any; + bind(...args: any[]): any; + bindAll(...args: any[]): any; + bindKey(...args: any[]): any; + camelCase(...args: any[]): any; + capitalize(...args: any[]): any; + castArray(...args: any[]): any; + ceil(...args: any[]): any; + chain(): any; + chunk(...args: any[]): any; + clamp(...args: any[]): any; + clone(...args: any[]): any; + cloneDeep(...args: any[]): any; + cloneDeepWith(...args: any[]): any; + cloneWith(...args: any[]): any; + commit(): any; + compact(...args: any[]): any; + concat(...args: any[]): any; + cond(...args: any[]): any; + conforms(...args: any[]): any; + conformsTo(...args: any[]): any; + constant(...args: any[]): any; + countBy(...args: any[]): any; + create(...args: any[]): any; + curry(...args: any[]): any; + curryRight(...args: any[]): any; + debounce(...args: any[]): any; + deburr(...args: any[]): any; + defaultTo(...args: any[]): any; + defaults(...args: any[]): any; + defaultsDeep(...args: any[]): any; + defer(...args: any[]): any; + delay(...args: any[]): any; + difference(...args: any[]): any; + differenceBy(...args: any[]): any; + differenceWith(...args: any[]): any; + divide(...args: any[]): any; + drop(...args: any[]): any; + dropRight(...args: any[]): any; + dropRightWhile(...args: any[]): any; + dropWhile(...args: any[]): any; + each(...args: any[]): any; + eachRight(...args: any[]): any; + endsWith(...args: any[]): any; + entries(...args: any[]): any; + entriesIn(...args: any[]): any; + eq(...args: any[]): any; + escape(...args: any[]): any; + escapeRegExp(...args: any[]): any; + every(...args: any[]): any; + extend(...args: any[]): any; + extendWith(...args: any[]): any; + fill(...args: any[]): any; + filter(...args: any[]): any; + find(...args: any[]): any; + findIndex(...args: any[]): any; + findKey(...args: any[]): any; + findLast(...args: any[]): any; + findLastIndex(...args: any[]): any; + findLastKey(...args: any[]): any; + first(...args: any[]): any; + flatMap(...args: any[]): any; + flatMapDeep(...args: any[]): any; + flatMapDepth(...args: any[]): any; + flatten(...args: any[]): any; + flattenDeep(...args: any[]): any; + flattenDepth(...args: any[]): any; + flip(...args: any[]): any; + floor(...args: any[]): any; + flow(...args: any[]): any; + flowRight(...args: any[]): any; + forEach(...args: any[]): any; + forEachRight(...args: any[]): any; + forIn(...args: any[]): any; + forInRight(...args: any[]): any; + forOwn(...args: any[]): any; + forOwnRight(...args: any[]): any; + fromPairs(...args: any[]): any; + functions(...args: any[]): any; + functionsIn(...args: any[]): any; + get(...args: any[]): any; + groupBy(...args: any[]): any; + gt(...args: any[]): any; + gte(...args: any[]): any; + has(...args: any[]): any; + hasIn(...args: any[]): any; + head(...args: any[]): any; + identity(...args: any[]): any; + inRange(...args: any[]): any; + includes(...args: any[]): any; + indexOf(...args: any[]): any; + initial(...args: any[]): any; + intersection(...args: any[]): any; + intersectionBy(...args: any[]): any; + intersectionWith(...args: any[]): any; + invert(...args: any[]): any; + invertBy(...args: any[]): any; + invoke(...args: any[]): any; + invokeMap(...args: any[]): any; + isArguments(...args: any[]): any; + isArray(...args: any[]): any; + isArrayBuffer(...args: any[]): any; + isArrayLike(...args: any[]): any; + isArrayLikeObject(...args: any[]): any; + isBoolean(...args: any[]): any; + isBuffer(...args: any[]): any; + isDate(...args: any[]): any; + isElement(...args: any[]): any; + isEmpty(...args: any[]): any; + isEqual(...args: any[]): any; + isEqualWith(...args: any[]): any; + isError(...args: any[]): any; + isFinite(...args: any[]): any; + isFunction(...args: any[]): any; + isInteger(...args: any[]): any; + isLength(...args: any[]): any; + isMap(...args: any[]): any; + isMatch(...args: any[]): any; + isMatchWith(...args: any[]): any; + isNaN(...args: any[]): any; + isNative(...args: any[]): any; + isNil(...args: any[]): any; + isNull(...args: any[]): any; + isNumber(...args: any[]): any; + isObject(...args: any[]): any; + isObjectLike(...args: any[]): any; + isPlainObject(...args: any[]): any; + isRegExp(...args: any[]): any; + isSafeInteger(...args: any[]): any; + isSet(...args: any[]): any; + isString(...args: any[]): any; + isSymbol(...args: any[]): any; + isTypedArray(...args: any[]): any; + isUndefined(...args: any[]): any; + isWeakMap(...args: any[]): any; + isWeakSet(...args: any[]): any; + iteratee(...args: any[]): any; + join(...args: any[]): any; + kebabCase(...args: any[]): any; + keyBy(...args: any[]): any; + keys(...args: any[]): any; + keysIn(...args: any[]): any; + last(...args: any[]): any; + lastIndexOf(...args: any[]): any; + lowerCase(...args: any[]): any; + lowerFirst(...args: any[]): any; + lt(...args: any[]): any; + lte(...args: any[]): any; + map(...args: any[]): any; + mapKeys(...args: any[]): any; + mapValues(...args: any[]): any; + matches(...args: any[]): any; + matchesProperty(...args: any[]): any; + max(...args: any[]): any; + maxBy(...args: any[]): any; + mean(...args: any[]): any; + meanBy(...args: any[]): any; + memoize(...args: any[]): any; + merge(...args: any[]): any; + mergeWith(...args: any[]): any; + method(...args: any[]): any; + methodOf(...args: any[]): any; + min(...args: any[]): any; + minBy(...args: any[]): any; + mixin(...args: any[]): any; + multiply(...args: any[]): any; + negate(...args: any[]): any; + next(): any; + noConflict(...args: any[]): any; + noop(...args: any[]): any; + now(...args: any[]): any; + nth(...args: any[]): any; + nthArg(...args: any[]): any; + omit(...args: any[]): any; + omitBy(...args: any[]): any; + once(...args: any[]): any; + orderBy(...args: any[]): any; + over(...args: any[]): any; + overArgs(...args: any[]): any; + overEvery(...args: any[]): any; + overSome(...args: any[]): any; + pad(...args: any[]): any; + padEnd(...args: any[]): any; + padStart(...args: any[]): any; + parseInt(...args: any[]): any; + partial(...args: any[]): any; + partialRight(...args: any[]): any; + partition(...args: any[]): any; + pick(...args: any[]): any; + pickBy(...args: any[]): any; + plant(value: any): any; + pop(...args: any[]): any; + property(...args: any[]): any; + propertyOf(...args: any[]): any; + pull(...args: any[]): any; + pullAll(...args: any[]): any; + pullAllBy(...args: any[]): any; + pullAllWith(...args: any[]): any; + pullAt(...args: any[]): any; + push(...args: any[]): any; + random(...args: any[]): any; + range(...args: any[]): any; + rangeRight(...args: any[]): any; + rearg(...args: any[]): any; + reduce(...args: any[]): any; + reduceRight(...args: any[]): any; + reject(...args: any[]): any; + remove(...args: any[]): any; + repeat(...args: any[]): any; + replace(...args: any[]): any; + rest(...args: any[]): any; + result(...args: any[]): any; + reverse(): any; + round(...args: any[]): any; + runInContext(...args: any[]): any; + sample(...args: any[]): any; + sampleSize(...args: any[]): any; + set(...args: any[]): any; + setWith(...args: any[]): any; + shift(...args: any[]): any; + shuffle(...args: any[]): any; + size(...args: any[]): any; + slice(...args: any[]): any; + snakeCase(...args: any[]): any; + some(...args: any[]): any; + sort(...args: any[]): any; + sortBy(...args: any[]): any; + sortedIndex(...args: any[]): any; + sortedIndexBy(...args: any[]): any; + sortedIndexOf(...args: any[]): any; + sortedLastIndex(...args: any[]): any; + sortedLastIndexBy(...args: any[]): any; + sortedLastIndexOf(...args: any[]): any; + sortedUniq(...args: any[]): any; + sortedUniqBy(...args: any[]): any; + splice(...args: any[]): any; + split(...args: any[]): any; + spread(...args: any[]): any; + startCase(...args: any[]): any; + startsWith(...args: any[]): any; + stubArray(...args: any[]): any; + stubFalse(...args: any[]): any; + stubObject(...args: any[]): any; + stubString(...args: any[]): any; + stubTrue(...args: any[]): any; + subtract(...args: any[]): any; + sum(...args: any[]): any; + sumBy(...args: any[]): any; + tail(...args: any[]): any; + take(...args: any[]): any; + takeRight(...args: any[]): any; + takeRightWhile(...args: any[]): any; + takeWhile(...args: any[]): any; + tap(...args: any[]): any; + template(...args: any[]): any; + throttle(...args: any[]): any; + thru(...args: any[]): any; + times(...args: any[]): any; + toArray(...args: any[]): any; + toFinite(...args: any[]): any; + toInteger(...args: any[]): any; + toJSON(): any; + toLength(...args: any[]): any; + toLower(...args: any[]): any; + toNumber(...args: any[]): any; + toPairs(...args: any[]): any; + toPairsIn(...args: any[]): any; + toPath(...args: any[]): any; + toPlainObject(...args: any[]): any; + toSafeInteger(...args: any[]): any; + toUpper(...args: any[]): any; + transform(...args: any[]): any; + trim(...args: any[]): any; + trimEnd(...args: any[]): any; + trimStart(...args: any[]): any; + truncate(...args: any[]): any; + unary(...args: any[]): any; + unescape(...args: any[]): any; + union(...args: any[]): any; + unionBy(...args: any[]): any; + unionWith(...args: any[]): any; + uniq(...args: any[]): any; + uniqBy(...args: any[]): any; + uniqWith(...args: any[]): any; + uniqueId(...args: any[]): any; + unset(...args: any[]): any; + unshift(...args: any[]): any; + unzip(...args: any[]): any; + unzipWith(...args: any[]): any; + update(...args: any[]): any; + updateWith(...args: any[]): any; + upperCase(...args: any[]): any; + upperFirst(...args: any[]): any; + value(): any; + valueOf(): any; + values(...args: any[]): any; + valuesIn(...args: any[]): any; + without(...args: any[]): any; + words(...args: any[]): any; + wrap(...args: any[]): any; + xor(...args: any[]): any; + xorBy(...args: any[]): any; + xorWith(...args: any[]): any; + zip(...args: any[]): any; + zipObject(...args: any[]): any; + zipObjectDeep(...args: any[]): any; + zipWith(...args: any[]): any; +} +declare namespace example { + function bind(...args: any[]): any; + namespace bind { + // Circular reference from example.bind + const placeholder: any; + } + function bindKey(...args: any[]): any; + namespace bindKey { + // Circular reference from example.bindKey + const placeholder: any; + } + function curry(func: any, arity: any, guard: any): any; + namespace curry { + // Circular reference from example.curry + const placeholder: any; + } + function curryRight(func: any, arity: any, guard: any): any; + namespace curryRight { + // Circular reference from example.curryRight + const placeholder: any; + } + function memoize(func: any, resolver: any): any; + namespace memoize { + class Cache { + constructor(entries: any); + clear(): void; + get(key: any): any; + has(key: any): any; + set(key: any, value: any): any; + } + } + function partial(...args: any[]): any; + namespace partial { + // Circular reference from example.partial + const placeholder: any; + } + function partialRight(...args: any[]): any; + namespace partialRight { + // Circular reference from example.partialRight + const placeholder: any; + } +} \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixCannotFindModule_all.ts b/tests/cases/fourslash/codeFixCannotFindModule_all.ts index 044d0c5deb01e..1b1b36d13ad3a 100644 --- a/tests/cases/fourslash/codeFixCannotFindModule_all.ts +++ b/tests/cases/fourslash/codeFixCannotFindModule_all.ts @@ -21,12 +21,11 @@ test.setTypesRegistry({ goTo.marker(); verify.codeFixAll({ - fixId: "fixCannotFindModule", + fixId: "installTypesPackage", fixAllDescription: "Install all missing types packages", + newFileContent: {}, // no changes commands: [ { packageName: "@types/abs", file: "/a.ts", type: "install package" }, { packageName: "@types/zap", file: "/a.ts", type: "install package" }, ], - newFileContent: `import * as abs from "abs"; -import * as zap from "zap";` // unchanged }); diff --git a/tests/cases/fourslash/codeFixCannotFindModule_generateTypes.ts b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes.ts new file mode 100644 index 0000000000000..af8707a9f81c5 --- /dev/null +++ b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes.ts @@ -0,0 +1,44 @@ +/// + +// @Filename: /dir/node_modules/plus/index.js +////module.exports = function plus(x, y) { return x + y; }; + +// @Filename: /dir/a.ts +////import plus = require("plus"); +////plus; + +// @Filename: /dir/tsconfig.json +////{ +//// "compilerOptions": { +//// "paths": { +//// "nota": ["valid_pathmapping"], +//// dontCrash: 37, +//// "*": ["abc123"], +//// } +//// } +////} + +goTo.file("/dir/a.ts"); +verify.codeFix({ + description: "Generate types for 'plus'", + newFileContent: { + "/dir/tsconfig.json": +`{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": ["types/*"], + "nota": ["valid_pathmapping"], + dontCrash: 37, + "*": ["abc123"], + } + } +}`, + }, + commands: [{ + type: "generate types", + file: "/dir/a.ts", + fileToGenerateTypesFor: "/dir/node_modules/plus/index.js", + outputFileName: "/dir/types/plus.d.ts", + }], +}); diff --git a/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_all.ts b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_all.ts new file mode 100644 index 0000000000000..183ca3324a473 --- /dev/null +++ b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_all.ts @@ -0,0 +1,47 @@ +/// + +// @Filename: /dir/node_modules/plus/index.js +////module.exports = function plus(x, y) { return x + y; }; + +// @Filename: /dir/node_modules/times/index.js +////module.exports = function times(x, y) { return x * y; }; + +// @Filename: /dir/a.ts +////import plus = require("plus"); +////import times = require("times"); +////plus; + +// @Filename: /dir/tsconfig.json +////{ +//// "compilerOptions": {} +////} + +goTo.file("/dir/a.ts"); +verify.codeFixAll({ + fixAllDescription: "Generate types for all packages without types", + fixId: "generateTypes", + newFileContent: { + "/dir/tsconfig.json": +// TODO: GH#26618 +`{ + "compilerOptions": { + "baseUrl": ".", + "paths": { "*": ["types/*"] }, +} +}`, + }, + commands: [ + { + type: "generate types", + file: "/dir/a.ts", + fileToGenerateTypesFor: "/dir/node_modules/plus/index.js", + outputFileName: "/dir/types/plus.d.ts", + }, + { + type: "generate types", + file: "/dir/a.ts", + fileToGenerateTypesFor: "/dir/node_modules/times/index.js", + outputFileName: "/dir/types/times.d.ts", + }, + ], +}); diff --git a/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_noExistingCompilerOptions.ts b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_noExistingCompilerOptions.ts new file mode 100644 index 0000000000000..99a2207814391 --- /dev/null +++ b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_noExistingCompilerOptions.ts @@ -0,0 +1,28 @@ +/// + +// @Filename: /dir/node_modules/plus/index.js +////module.exports = function plus(x, y) { return x * y; }; + +// @Filename: /dir/a.ts +////import plus = require("plus"); +////plus; + +// @Filename: /dir/tsconfig.json +////{} + +goTo.file("/dir/a.ts"); +verify.codeFix({ + description: "Generate types for 'plus'", + newFileContent: { + "/dir/tsconfig.json": +`{ + "compilerOptions": { "baseUrl": ".", "paths": { "*": ["types/*"] } }, +}`, + }, + commands: [{ + type: "generate types", + file: "/dir/a.ts", + fileToGenerateTypesFor: "/dir/node_modules/plus/index.js", + outputFileName: "/dir/types/plus.d.ts", + }], +}); diff --git a/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_notForNonexistentPackage.ts b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_notForNonexistentPackage.ts new file mode 100644 index 0000000000000..a4e6450e93351 --- /dev/null +++ b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_notForNonexistentPackage.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.ts +////import plus = require("plus"); +////plus; + +// @Filename: /tsconfig.json +////{ +//// "compilerOptions": {} +////} + +goTo.file("/a.ts"); +verify.codeFixAvailable([]); diff --git a/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_typesDirectoryExists.ts b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_typesDirectoryExists.ts new file mode 100644 index 0000000000000..5c095f84ba752 --- /dev/null +++ b/tests/cases/fourslash/codeFixCannotFindModule_generateTypes_typesDirectoryExists.ts @@ -0,0 +1,30 @@ +/// + +// @Filename: /dir/node_modules/plus/index.js +////module.exports = function plus(x, y) { return x * y; }; + +// @Filename: /dir/a.ts +////import plus = require("plus"); +////plus; + +// @Filename: /dir/tsconfig.json +////{ +//// "compilerOptions": { +//// "baseUrl": "base", +//// "paths": { +//// "*": ["myTypes/*"], +//// }, +//// }, +////} + +goTo.file("/dir/a.ts"); +verify.codeFix({ + description: "Generate types for 'plus'", + newFileContent: {}, // no change + commands: [{ + type: "generate types", + file: "/dir/a.ts", + fileToGenerateTypesFor: "/dir/node_modules/plus/index.js", + outputFileName: "/dir/base/myTypes/plus.d.ts", + }], +}); diff --git a/tests/cases/fourslash/codeFixGenerateDefinitions.ts b/tests/cases/fourslash/codeFixGenerateDefinitions.ts new file mode 100644 index 0000000000000..41dc24d3a3cbd --- /dev/null +++ b/tests/cases/fourslash/codeFixGenerateDefinitions.ts @@ -0,0 +1,9 @@ +/// + +// @Filename: /node_modules/foo/index.d.ts +////module.exports = 0; + +// @Filename: /a.ts +////import * as foo from "foo"; + +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts b/tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts index 1389a50935b92..478b2a7c64751 100644 --- a/tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts +++ b/tests/cases/fourslash/codeFixUndeclaredMethodFunctionArgs.ts @@ -28,5 +28,5 @@ verify.codeFix({ foo1(arg0: () => number, arg1: () => string, arg2: () => boolean): any { throw new Error("Method not implemented."); } - ` + `, }); diff --git a/tests/cases/fourslash/formatWithTabs.ts b/tests/cases/fourslash/formatWithTabs.ts index c33808bd26acf..e1b8878f4a9b3 100644 --- a/tests/cases/fourslash/formatWithTabs.ts +++ b/tests/cases/fourslash/formatWithTabs.ts @@ -13,4 +13,4 @@ format.document(); verify.currentFileContentIs( `const foo = [ 1 -];`); \ No newline at end of file +];`); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 8a91718dd7b4f..f0329d9a1e6ca 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -284,7 +284,7 @@ declare namespace FourSlashInterface { docCommentTemplateAt(markerName: string | FourSlashInterface.Marker, expectedOffset: number, expectedText: string): void; noDocCommentTemplateAt(markerName: string | FourSlashInterface.Marker): void; rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void; - codeFixAll(options: { fixId: string, fixAllDescription: string, newFileContent: string, commands?: {}[] }): void; + codeFixAll(options: { fixId: string, fixAllDescription: string, newFileContent: NewFileContent, commands?: {}[] }): void; 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; @@ -348,6 +348,8 @@ declare namespace FourSlashInterface { readonly preferences?: UserPreferences; }): void; noMoveToNewFile(): void; + + generateTypes(...options: GenerateTypesOptions[]): void; } class edit { backspace(count?: number): void; @@ -635,6 +637,13 @@ declare namespace FourSlashInterface { text: string | undefined; } + interface GenerateTypesOptions { + readonly name?: string; + readonly value: unknown; + readonly output?: string | undefined; + readonly outputBaseline?: string; + } + type ArrayOrSingle = T | ReadonlyArray; type NewFileContent = string | { readonly [fileName: string]: string }; } diff --git a/tests/cases/fourslash/generateTypes.ts b/tests/cases/fourslash/generateTypes.ts new file mode 100644 index 0000000000000..bfe521e204666 --- /dev/null +++ b/tests/cases/fourslash/generateTypes.ts @@ -0,0 +1,109 @@ +/// + +////dummy text + +verify.generateTypes( +{ + value: 0, + output: +`export = example; +declare const example: number;`, +}, +{ + value: (x, y) => x + y, + output: +`export = example; +declare function example(x: any, y: any): void;`, +}, +{ + // non-arrow functions have different toString(), so important to test + value: function(x, y) { + return x * y; + function inner() { + arguments; // Should not affect type inference + } + }, + output: +`export = example; +declare function example(x: any, y: any): any;`, +}, +{ + value: function(x) { arguments; }, + output: +`export = example; +declare function example(x: any, ...args: any[]): void;`, +}, + +{ + value: ({ default() {} }), + output: +`export default function _default(): void;`, +}, + +{ + value: ({ default: class {} }), + output: +`export default class _default { +}`, +}, + +{ + value: new Date(), + output: +`export = example; +declare const example: Date;`, +}, + +{ + value: [0], + output: +`export = example; +declare const example: number[];`, +}, +{ + value: [() => 0, () => ""], + output: +`export = example; +declare const example: Function[];`, +}, +{ + value: (() => { + const a = []; + a.push(a); + return a; + })(), + output: +`export = example; +declare const example: any[];`, +}, +{ + value: (() => { + const o = { + default: 0, + a: 0, + b: "", + self: null, + fn: x => x, + ns1: { x: 0, default: 0 }, + ns2: { fn: x => x, default: 0 }, + }; + o.self = o; + return o; + })(), + output: +`export const a: number; +export const b: string; +export default _default; +export const _default: number; +export function fn(x: any): void; +export const ns1: { + default: number; + x: number; +}; +export namespace ns2 { + function fn(x: any): void; +} +// Circular reference from example +export const self: any;`, +}, +); diff --git a/tests/cases/fourslash/generateTypes_baselines.ts b/tests/cases/fourslash/generateTypes_baselines.ts new file mode 100644 index 0000000000000..a8363b302837a --- /dev/null +++ b/tests/cases/fourslash/generateTypes_baselines.ts @@ -0,0 +1,35 @@ +/// + +////dummy text + +verify.generateTypes( + // would like to test against the real "global" but that may vary between node versions. + { + value: { + Array: ignore(Array, ["values"]), + Boolean, + Date, + Math, + Number, + RegExp, + String: ignore(String, ["padStart", "padEnd", "trimStart", "trimEnd"]), + Symbol: ignore(Symbol, ["asyncIterator"]), + }, + outputBaseline: "global", + }, + { value: require("lodash"), outputBaseline: "lodash" }, +); + +// Ignore properties only present in newer versions of node. +function ignore(Cls: Function, ignored: ReadonlyArray): Function { + class Copy {} + for (const name of Object.getOwnPropertyNames(Cls)) { + if (ignored.includes(name) || name === "arguments" || name === "caller" || name === "name" || name === "length" || name === "prototype") continue; + Copy[name] = Cls[name]; + } + for (const name of Object.getOwnPropertyNames(Cls.prototype)) { + if (ignored.includes(name)) continue; + Copy.prototype[name] = Cls.prototype[name]; + } + return Copy; +} diff --git a/tests/cases/fourslash/generateTypes_classes.ts b/tests/cases/fourslash/generateTypes_classes.ts new file mode 100644 index 0000000000000..94fd43fe529d0 --- /dev/null +++ b/tests/cases/fourslash/generateTypes_classes.ts @@ -0,0 +1,109 @@ +/// + +////dummy text + +verify.generateTypes( +{ + value: class {}, + output: +`export = example; +declare class example { +}`, +}, + +{ + value: class { + constructor(x) { + (this as any).x = x; + // Code inside this function should be ignored + function f(this: any) { + this.y = 0; + } + // Same for this class + class Other { constructor() { (this as any).z = 0; } } + } + }, + output: +`export = example; +declare class example { + constructor(x: any); + x: any; +}`, +}, +{ + value: { x: 0, export: 0 }, + output: `export const x: number;`, +}, +{ + value: (() => { + class Super { + superField = 0; // TODO: climb to prototype.constructor and get instance fields? + superMethod() {} + static superStaticMethod() {} + } + class C extends Super { + constructor() { + super(); + (this as any)._privateField = 0; + (this as any).field = 0; + } + + _privateMethod() {} + method(_p) { + (this as any).otherField = 0; // TODO: include this in output? + } + + static _privateStatic() {} + static staticMethod(_s: any) {} + static staticMethodWithNoNamespaceMembers(_p: any) {} + + static _privateStaticField = 0; + static staticField = 0; + static innerClass = class {}; + } + (C.prototype as any).prototypeNonFunction = 0; // ignored + (C.staticMethod as any).staticMethodProperty = 0; + (C.staticMethod as any)._staticFieldPrivateMember = 0; + (C.prototype.method as any).methodMember = 0; // ignored + // Non-identifier names should be ignored. + (C as any)["&"] = function() {}; + (C.prototype as any)["&"] = function() {}; + return C; + })(), + output: +`export = example; +declare class example { + static staticField: number; + static staticMethodWithNoNamespaceMembers(_p: any): void; + static superStaticMethod(): void; + field: any; + method(_p: any): void; + superMethod(): void; +} +declare namespace example { + class innerClass { + } + function staticMethod(_s: any): void; + namespace staticMethod { + const staticMethodProperty: number; + } +}`, +}, + +{ + value: (() => { + function F() { this.x = 0; } + (F as any).staticMethod = function() {} + F.prototype.method = function() { } + return F; + })(), + output: +`export = example; +declare class example { + static staticMethod(): void; + x: any; + method(): void; +}`, +}, + +);