Remove "generate types" code (#31075)

This commit is contained in:
Ryan Cavanaugh
2019-04-23 13:51:47 -07:00
committed by GitHub
parent b47194bfa1
commit 885d4d63c8
29 changed files with 18 additions and 2058 deletions
-8
View File
@@ -4919,14 +4919,6 @@
"category": "Message",
"code": 95066
},
"Generate types for '{0}'": {
"category": "Message",
"code": 95067
},
"Generate types for all packages without types": {
"category": "Message",
"code": 95068
},
"Add 'unknown' conversion for non-overlapping types": {
"category": "Message",
"code": 95069
-162
View File
@@ -1,162 +0,0 @@
/* @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<ValueInfo>;
readonly namespaceMembers: ReadonlyArray<ValueInfo>;
}
export interface ValueInfoArray extends ValueInfoBase {
readonly kind: ValueKind.Array;
readonly inner: ValueInfo;
}
export interface ValueInfoObject extends ValueInfoBase {
readonly kind: ValueKind.Object;
readonly hasNontrivialPrototype: boolean;
readonly members: ReadonlyArray<ValueInfo>;
}
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 = <T>(obj: unknown, name: string, cbOk: () => T, cbFail: (isCircularReference: boolean, keyStack: ReadonlyArray<string>) => T) => T;
function getRecurser(): Recurser {
const seen: unknown[] = [];
const nameStack: string[] = [];
return (obj, name, cbOk, cbFail) => {
if (seen.indexOf(obj) !== -1 || nameStack.length > 4) {
return cbFail(seen.indexOf(obj) !== -1, nameStack);
}
seen.push(obj);
nameStack.push(name);
const res = cbOk();
nameStack.pop();
seen.pop();
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);
const hasNontrivialPrototype = Object.getPrototypeOf(value) !== Object.prototype;
const members = flatMap(entries, ({ key, value }) => getValueInfo(key, value, recurser));
return { kind: ValueKind.Object, name, hasNontrivialPrototype, members };
}
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<AnyConstructor> = memoize(() => {
const map = createMap<AnyConstructor>();
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<ValueInfo> {
const prototype = fn.prototype as unknown;
// tslint:disable-next-line no-unnecessary-type-assertion (TODO: update LKG and it will really be unnecessary)
return typeof prototype !== "object" || prototype === null ? emptyArray : mapDefined(getEntriesOfObject(prototype as object), ({ key, value }) =>
key === "constructor" ? undefined : getValueInfo(key, value, recurser));
}
const ignoredProperties: ReadonlyArray<string> = ["arguments", "caller", "constructor", "eval", "super_"];
const reservedFunctionProperties: ReadonlyArray<string> = Object.getOwnPropertyNames(noop);
interface ObjectEntry { readonly key: string; readonly value: unknown; }
function getEntriesOfObject(obj: object): ReadonlyArray<ObjectEntry> {
const seen = createMap<true>();
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.indexOf(key) === -1 &&
(typeof obj !== "function" || reservedFunctionProperties.indexOf(key) === -1) &&
// 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 startsWith(name, "_");
}
function tryRequire(fileNameToRequire: string): unknown {
try {
return require(fileNameToRequire);
}
catch {
return undefined;
}
}
}
-1
View File
@@ -53,6 +53,5 @@
"moduleSpecifiers.ts",
"watch.ts",
"tsbuild.ts",
"inspectValue.ts",
]
}
-26
View File
@@ -3068,19 +3068,6 @@ Actual: ${stringify(fullActual)}`);
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences) || ts.emptyArray;
}
public generateTypes(examples: ReadonlyArray<FourSlashInterface.GenerateTypesOptions>): void {
for (const { name = "example", value, global, output, outputBaseline } of examples) {
const actual = (global ? ts.generateTypesForGlobal : 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`);
}
}
}
public configurePlugin(pluginName: string, configuration: any): void {
(<ts.server.SessionClient>this.languageService).configurePlugin(pluginName, configuration);
}
@@ -4140,19 +4127,6 @@ 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;
readonly global?: boolean;
// Exactly one of these should be set:
readonly output?: string;
readonly outputBaseline?: string;
}
export class Edit {
-1
View File
@@ -4,7 +4,6 @@ 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";
+3 -16
View File
@@ -2,14 +2,13 @@ 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";
export type EventInitializationFailed = "event::initializationFailed";
export interface TypingInstallerResponse {
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | ActionValueInspected | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
}
export interface TypingInstallerRequestWithProjectName {
@@ -17,7 +16,7 @@ declare namespace ts.server {
}
/* @internal */
export type TypingInstallerRequestUnion = DiscoverTypings | CloseProject | TypesRegistryRequest | InstallPackageRequest | InspectValueRequest;
export type TypingInstallerRequestUnion = DiscoverTypings | CloseProject | TypesRegistryRequest | InstallPackageRequest;
export interface DiscoverTypings extends TypingInstallerRequestWithProjectName {
readonly fileNames: string[];
@@ -44,12 +43,6 @@ 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;
@@ -62,12 +55,6 @@ 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;
@@ -115,5 +102,5 @@ declare namespace ts.server {
}
/* @internal */
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InspectValueResponse | InstallTypes | InitializationFailedResponse;
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse;
}
-4
View File
@@ -277,10 +277,6 @@ namespace ts.server {
installPackage(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult> {
return this.typingsCache.installPackage({ ...options, projectName: this.projectName, projectRootPath: this.toPath(this.currentDirectory) });
}
/* @internal */
inspectValue(options: InspectValueOptions): Promise<ValueInfo> {
return this.typingsCache.inspectValue(options);
}
private get typingsCache(): TypingsCache {
return this.projectService.typingsCache;
-7
View File
@@ -8,8 +8,6 @@ namespace ts.server {
export interface ITypingsInstaller {
isKnownTypesPackageName(name: string): boolean;
installPackage(options: InstallPackageOptionsWithProject): Promise<ApplyCodeActionCommandResult>;
/* @internal */
inspectValue(options: InspectValueOptions): Promise<ValueInfo>;
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string> | undefined): void;
attach(projectService: ProjectService): void;
onProjectClosed(p: Project): void;
@@ -20,7 +18,6 @@ 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,
@@ -98,10 +95,6 @@ namespace ts.server {
return this.installer.installPackage(options);
}
inspectValue(options: InspectValueOptions): Promise<ValueInfo> {
return this.installer.inspectValue(options);
}
enqueueInstallTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string> | undefined, forceRefresh: boolean) {
const typeAcquisition = project.getTypeAcquisition();
+3 -93
View File
@@ -2,7 +2,6 @@
namespace ts.codefix {
const fixName = "fixCannotFindModule";
const fixIdInstallTypesPackage = "installTypesPackage";
const fixIdGenerateTypes = "generateTypes";
const errorCodeCannotFindModule = Diagnostics.Cannot_find_module_0.code;
const errorCodes = [
@@ -17,13 +16,12 @@ namespace ts.codefix {
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],
fixIds: [fixIdInstallTypesPackage],
getAllCodeActions: context => {
let savedTypesDir: string | null | undefined = null; // tslint:disable-line no-null-keyword
return codeFixAll(context, errorCodes, (changes, diag, commands) => {
return codeFixAll(context, errorCodes, (_changes, diag, commands) => {
const packageName = tryGetImportedPackageName(diag.file, diag.start);
if (packageName === undefined) return undefined;
switch (context.fixId) {
@@ -34,12 +32,6 @@ namespace ts.codefix {
}
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}`);
}
@@ -47,88 +39,6 @@ namespace ts.codefix {
},
});
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 = tryResolveJSModule(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 = findJsonProperty(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 = findJsonProperty(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 = findJsonProperty(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 getInstallCommand(fileName: string, packageName: string): InstallPackageAction {
return { type: "install package", file: fileName, packageName };
}
-252
View File
@@ -1,252 +0,0 @@
namespace ts {
export function generateTypesForModule(name: string, moduleValue: unknown, formatSettings: FormatCodeSettings): string {
return generateTypesForModuleOrGlobal(name, moduleValue, formatSettings, OutputKind.ExportEquals);
}
export function generateTypesForGlobal(name: string, globalValue: unknown, formatSettings: FormatCodeSettings): string {
return generateTypesForModuleOrGlobal(name, globalValue, formatSettings, OutputKind.Global);
}
function generateTypesForModuleOrGlobal(name: string, globalValue: unknown, formatSettings: FormatCodeSettings, outputKind: OutputKind.ExportEquals | OutputKind.Global): string {
return valueInfoToDeclarationFileText(inspectValue(name, globalValue), formatSettings, outputKind);
}
/* @internal */
export function valueInfoToDeclarationFileText(valueInfo: ValueInfo, formatSettings: FormatCodeSettings, outputKind: OutputKind.ExportEquals | OutputKind.Global = OutputKind.ExportEquals): string {
return textChanges.getNewFileText(toStatements(valueInfo, outputKind), ScriptKind.TS, formatSettings.newLineCharacter || "\n", formatting.getFormatContext(formatSettings));
}
/* @internal */
const enum OutputKind { ExportEquals, NamedExport, NamespaceMember, Global }
function toNamespaceMemberStatements(info: ValueInfo): ReadonlyArray<Statement> {
return toStatements(info, OutputKind.NamespaceMember);
}
function toStatements(info: ValueInfo, kind: OutputKind): ReadonlyArray<Statement> {
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.Global || 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, hasNontrivialPrototype } = info;
if (!hasNontrivialPrototype) {
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<Statement> {
const fnAst = parseClassOrFunctionBody(source);
const { parameters, returnType } = fnAst === undefined ? { parameters: emptyArray, returnType: anyType() } : getParametersAndReturnType(fnAst);
const protoOrInstanceMembers = createMap<MethodDeclaration | PropertyDeclaration>();
if (typeof fnAst === "object") getConstructorFunctionInstanceProperties(fnAst, protoOrInstanceMembers);
for (const p of prototypeMembers) {
// ignore non-functions on the prototype
if (p.kind === ValueKind.FunctionOrClass) {
const m = tryGetMethod(p);
if (m) {
protoOrInstanceMembers.set(p.name, m);
}
}
}
const classStaticMembers: ClassElement[] | undefined =
protoOrInstanceMembers.size !== 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;
}
}
break;
default:
Debug.assertNever(info);
}
}
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),
...arrayFrom(protoOrInstanceMembers.values()),
])
: 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, toPropertyName(m.name), /*questionToken*/ undefined, toType(m), /*initializer*/ undefined)));
default:
return Debug.assertNever(info);
}
}
function toPropertyName(name: string): Identifier | StringLiteral {
return isIdentifierText(name, ScriptTarget.ESNext) ? createIdentifier(name) : createStringLiteral(name);
}
// Parses assignments to "this.x" in the constructor into class property declarations
function getConstructorFunctionInstanceProperties(fnAst: FunctionOrConstructorNode, members: Map<MethodDeclaration | PropertyDeclaration>): void {
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)) {
getOrUpdate(members, name, () => createProperty(/*decorators*/ undefined, /*modifiers*/ undefined, name, /*questionOrExclamationToken*/ undefined, anyType(), /*initializer*/ undefined));
}
}
});
}
interface ParametersAndReturnType { readonly parameters: ReadonlyArray<ParameterDeclaration>; 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<Modifier> | undefined;
function addComment<T extends Node>(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<Statement>): NamespaceDeclaration {
return createModuleDeclaration(/*decorators*/ undefined, modifiers, createIdentifier(name), createModuleBlock(statements), NodeFlags.Namespace) as NamespaceDeclaration;
}
}
+6 -21
View File
@@ -1833,30 +1833,15 @@ namespace ts {
function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]> {
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);
return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action);
}
function applySingleCodeActionCommand(action: CodeActionCommand, formatSettings: FormatCodeSettings | undefined): Promise<ApplyCodeActionCommandResult> {
function applySingleCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult> {
const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName);
switch (action.type) {
case "install package":
return host.installPackage
? 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.assertNever(action);
}
Debug.assertEqual(action.type, "install package");
return host.installPackage
? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName })
: Promise.reject("Host does not implement `installPackage`");
}
function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined {
-1
View File
@@ -73,7 +73,6 @@
"codefixes/helpers.ts",
"codefixes/fixInvalidImportSyntax.ts",
"codefixes/fixStrictClassInitialization.ts",
"codefixes/generateTypes.ts",
"codefixes/requireInTs.ts",
"codefixes/useDefaultImport.ts",
"codefixes/fixAddModuleReferTypeMissingTypeof.ts",
+1 -12
View File
@@ -230,7 +230,6 @@ namespace ts {
isKnownTypesPackageName?(name: string): boolean;
installPackage?(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult>;
/* @internal */ inspectValue?(options: InspectValueOptions): Promise<ValueInfo>;
writeFile?(fileName: string, content: string): void;
/* @internal */
@@ -534,7 +533,7 @@ 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 | GenerateTypesAction;
export type CodeActionCommand = InstallPackageAction;
export interface InstallPackageAction {
/* @internal */ readonly type: "install package";
@@ -542,16 +541,6 @@ namespace ts {
/* @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;
}
/**
* A set of one or more available refactoring actions, grouped under a parent refactoring.
*/
@@ -390,7 +390,6 @@ namespace ts.projectSystem {
const typingsInstaller: server.ITypingsInstaller = {
isKnownTypesPackageName: returnFalse,
installPackage: notImplemented,
inspectValue: notImplemented,
enqueueInstallTypingsRequest: (proj, typeAcquisition, unresolvedImports) => {
assert.isUndefined(request);
request = JSON.stringify(server.createInstallTypingsRequest(proj, typeAcquisition, unresolvedImports || server.emptyArray, cachePath));
+1 -12
View File
@@ -232,7 +232,6 @@ namespace ts.server {
private static readonly maxActiveRequestCount = 10;
private static readonly requestDelayMillis = 100;
private packageInstalledPromise: { resolve(value: ApplyCodeActionCommandResult): void, reject(reason: unknown): void } | undefined;
private inspectValuePromise: { resolve(value: ValueInfo): void } | undefined;
constructor(
private readonly telemetryEnabled: boolean,
@@ -270,12 +269,6 @@ namespace ts.server {
});
}
inspectValue(options: InspectValueOptions): Promise<ValueInfo> {
this.send<InspectValueRequest>({ kind: "inspectValue", options });
Debug.assert(this.inspectValuePromise === undefined);
return new Promise<ValueInfo>(resolve => { this.inspectValuePromise = { resolve }; });
}
attach(projectService: ProjectService) {
this.projectService = projectService;
if (this.logger.hasLevel(LogLevel.requestTime)) {
@@ -363,7 +356,7 @@ namespace ts.server {
}
}
private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | InspectValueResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Received response:${stringifyIndented(response)}`);
}
@@ -388,10 +381,6 @@ 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 = {
message: response.message
@@ -168,11 +168,6 @@ 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);
}