UNPKG

flowgen

Version:

Generate flowtype definition files from TypeScript

234 lines (192 loc) 9.37 kB
"use strict"; exports.__esModule = true; exports.declarationFileTransform = declarationFileTransform; exports.importEqualsTransformer = importEqualsTransformer; exports.importTypeToImportDeclaration = importTypeToImportDeclaration; exports.legacyModules = legacyModules; var ts = _interopRequireWildcard(require("typescript")); var _ast = require("./ast"); var logger = _interopRequireWildcard(require("../logger")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function updatePos(node) { // @ts-expect-error todo: modifying "readonly" property node.pos = 1; // @ts-expect-error todo: modifying "readonly" property node.end = 2; return node; } function importEqualsTransformer /*opts?: Opts*/ () { function visitor(ctx) { const visitor = node => { if (ts.isImportEqualsDeclaration(node)) { if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) { const importClause = ts.createImportClause(undefined, ts.createNamespaceImport(ts.createIdentifier(node.name.text))); const moduleSpecifier = ts.createLiteral( // @ts-expect-error todo(flow->ts) node.moduleReference.expression.text); const importNode = updatePos( //$todo Flow has problems when switching variables instead of literals ts.createImportDeclaration(undefined, undefined, //$todo Flow has problems when switching variables instead of literals updatePos(importClause), //$todo Flow has problems when switching variables instead of literals updatePos(moduleSpecifier))); return importNode; } else if (node.moduleReference.kind === ts.SyntaxKind.QualifiedName) { const varNode = updatePos( //$todo Flow has problems when switching variables instead of literals ts.createVariableStatement(node.modifiers, [ts.createVariableDeclaration(node.name, //$todo Flow has problems when switching variables instead of literals ts.createTypeQueryNode(node.moduleReference), undefined)])); return varNode; } } return ts.visitEachChild(node, visitor, ctx); }; return visitor; } return ctx => { return sf => ts.visitNode(sf, visitor(ctx)); }; } function legacyModules() { function visitor(ctx) { const visitor = node => { (0, _ast.stripDetailsFromTree)(node); if (ts.isModuleDeclaration(node)) { if (node.name.kind === ts.SyntaxKind.Identifier) { // @ts-expect-error todo: modifying "readonly" property node.flags |= ts.NodeFlags.Namespace; } visitor(node.body); return node; } return ts.visitEachChild(node, visitor, ctx); }; return visitor; } return ctx => { return sf => ts.visitNode(sf, visitor(ctx)); }; } function declarationFileTransform(options) { function visitor(ctx) { const visitor = node => { if (!(options != null && options.asModule) || !ts.isSourceFile(node)) { return node; } if (node.statements.some(statement => ts.isModuleDeclaration(statement))) { return node; } return ctx.factory.updateSourceFile(node, [ctx.factory.createModuleDeclaration(undefined, undefined, ctx.factory.createIdentifier(options.asModule), ctx.factory.createModuleBlock(node.statements.map(statement => { if (statement.modifiers) { // @ts-expect-error statement.modifiers = statement.modifiers.filter(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword); } return statement; })))]); }; return visitor; } return ctx => { return sf => ts.visitNode(sf, visitor(ctx)); }; } /** Like `ctx.factory.createQualifiedName`, but put identifier at start instead of end. */ function prependIdentifier(ctx, id, qualifier) { if (!qualifier) { return id; } else if (qualifier.kind === ts.SyntaxKind.Identifier) { return ctx.factory.createQualifiedName(id, qualifier); } else { return ctx.factory.createQualifiedName(prependIdentifier(ctx, id, qualifier.left), qualifier.right); } } /** * Make a deterministic, unique, JS-valid identifier based on the given string. * * The result is a string which begins with `prefix` and can be used as a * JavaScript identifier name. It's always the same when called with the * same arguments, and always different when called with different * arguments. * * The argument `prefix` is required to be a valid JS identifier name. */ function escapeNameAsIdentifierWithPrefix(prefix, name) { // The set of valid JS identifier names is defined here: // https://tc39.es/ecma262/#prod-IdentifierName // In particular, after the first character, each character can be `$` // or any character with the `ID_Continue` Unicode property, among others. // For our construction: // * Characters in `name` with `ID_Continue`, other than `_`, we use // verbatim. // * Other characters we replace with an escape sequence, marked by `_`. // This provides a string where all characters have `ID_Continue`, as an // invertible function of `name`. const escapedName = name.replace(/\P{ID_Continue}|_/gu, c => `_${c.codePointAt(0).toString(16)}_`); // This escaping always gives different results for different inputs, // because the following code would reconstruct the input: // assert( // name === // escapedName.replace(/_[^_]*_/g, s => // String.fromCodePoint(Number.parseInt(s.substring(1), 16)), // ), // ); // Then we delimit the prefix from the escaped name with `$`, which lacks // `ID_Continue` and therefore cannot appear in the escaped name. return prefix + "$" + escapedName; } /** * Rewrite `import(…)` types into full import declarations. * * Flow doesn't have an equivalent to `import(…)` types. */ function importTypeToImportDeclaration() { function visitor(ctx) { const imports = new Map(); const visitor = node => { if (ts.isImportTypeNode(node)) { if (!ts.isLiteralTypeNode(node.argument) || !ts.isStringLiteral(node.argument.literal)) { // TS (as of 4.6.2) gives an error if the argument to `import(…)` // isn't a string literal, saying "String literal expected." // So this case should be impossible. logger.error(node, { type: "UnexpectedTsSyntax", description: "import(…) type with argument not a string literal" }); return ts.visitEachChild(node, visitor, ctx); } const importSource = node.argument.literal.text; let identifier; if (!imports.has(importSource)) { identifier = ctx.factory.createIdentifier(escapeNameAsIdentifierWithPrefix("$Flowgen$Import", importSource)); // Construct an import statement like this: // import * as ${identifier} from ${node.argument}; const decl = ctx.factory.createImportDeclaration(undefined, undefined, ctx.factory.createImportClause(false, undefined, ctx.factory.createNamespaceImport(identifier)), node.argument.literal); imports.set(importSource, { identifier, decl }); } else { identifier = imports.get(importSource).identifier; } if (!node.qualifier) { // The reference is to the module as a whole, as a type. // Must need a `typeof`. if (node.typeArguments) throw new Error("impossible syntax: type arguments applied to a module"); return ctx.factory.createTypeOfExpression(identifier); } else { // The reference is to something inside the module. return ctx.factory.createTypeReferenceNode(prependIdentifier(ctx, identifier, node.qualifier), ts.visitNodes(node.typeArguments, visitor)); } } if (ts.isSourceFile(node)) { const visited = ts.visitEachChild(node, visitor, ctx); if (!imports.size) { return visited; } return ctx.factory.updateSourceFile(visited, [// @ts-expect-error ...[...imports.values()].map(v => v.decl), ...visited.statements]); } return ts.visitEachChild(node, visitor, ctx); }; return visitor; } return ctx => { return sf => ts.visitNode(sf, visitor(ctx)); }; }