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
+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.
*/