Generate TypeScript typings, and improve generated Flow typings (#7101)

* generate typescript types

* improve type generator output

* move generator scripts to scripts/generators

* use new stringifier for generating flow types too

* export summary types

* add support for oneOfNodeOrValueTypes to improve type generation

* export typescript types from top level, and remove module declaration

* generate typescript/flow types and copy typescript types to babel-types/lib as part of make build

* copy flow types to babel-types/lib as part of make build (fix #6839)

* improve typing: Identifier->name should be a string, not any

* avoid destructuring, to support node 4

* update doc generator to share more code, regenerate babel-types readme, pipe all generator output to stdout

* regenerate babel-types readme as part of make build

* improve typing: ClassProperty->key should be Identifier | StringLiteral | NumericLiteral | Expression, not any

* improve typing: optional node properties are nullable, not undefinedable

* improve docs: FlowClassImplements should be ClassImplements

* make ts usage more friendly: when using babel-types api, make optional params | undefined, and when reading nodes keep optional params | null

* rm lib/types.d.ts and lib/types.js in favor of packages/babel-types/lib

* add missing variance node type, address review comments

* add tests for flow variance

* Comment should be a disjoint union of tagged types

* update .flowconfig
This commit is contained in:
Boris Cherny
2018-01-17 07:31:46 -08:00
committed by Henry Zhu
parent 667f5815c1
commit c3654d83c8
18 changed files with 423 additions and 1980 deletions

View File

@@ -1,24 +1,22 @@
"use strict";
const util = require("util");
const path = require("path");
const fs = require("fs");
const utils = require("./utils");
const types = require("../packages/babel-types");
const types = require("../../packages/babel-types");
const readmePath = path.join(
__dirname,
"..",
"packages",
"babel-types",
"README.md"
);
const readmeSrc = fs.readFileSync(readmePath, "utf8");
const readme = [
readmeSrc.split("<!-- begin generated section -->")[0].trim(),
"",
"<!-- begin generated section -->",
"",
`# @babel/types
> This module contains methods for building ASTs manually and for checking the types of AST nodes.
## Install
\`\`\`sh
npm install --save-dev @babel/types
\`\`\`
## API`,
];
const customTypes = {
@@ -38,40 +36,6 @@ const customTypes = {
key: "if computed then `Expression` else `Identifier | Literal`",
},
};
function getType(validator) {
if (validator.type) {
return validator.type;
} else if (validator.oneOfNodeTypes) {
return validator.oneOfNodeTypes.join(" | ");
} else if (validator.oneOfNodeOrValueTypes) {
return validator.oneOfNodeOrValueTypes.join(" | ");
} else if (validator.oneOf) {
return validator.oneOf.map(val => util.inspect(val)).join(" | ");
} else if (validator.chainOf) {
if (
validator.chainOf.length === 2 &&
validator.chainOf[0].type === "array" &&
validator.chainOf[1].each
) {
return "Array<" + getType(validator.chainOf[1].each) + ">";
}
if (
validator.chainOf.length === 2 &&
validator.chainOf[0].type === "string" &&
validator.chainOf[1].oneOf
) {
return validator.chainOf[1].oneOf
.map(function(val) {
return JSON.stringify(val);
})
.join(" | ");
}
}
const err = new Error("Unrecognised validator type");
err.code = "UNEXPECTED_VALIDATOR_TYPE";
err.validator = validator;
throw err;
}
Object.keys(types.BUILDER_KEYS)
.sort()
.forEach(function(key) {
@@ -79,8 +43,7 @@ Object.keys(types.BUILDER_KEYS)
readme.push("```javascript");
readme.push(
"t." +
key[0].toLowerCase() +
key.substr(1) +
utils.toFunctionName(key) +
"(" +
types.BUILDER_KEYS[key].join(", ") +
")"
@@ -123,7 +86,9 @@ Object.keys(types.BUILDER_KEYS)
fieldDescription.push(`: ${customTypes[key][field]}`);
} else if (validator) {
try {
fieldDescription.push(": `" + getType(validator) + "`");
fieldDescription.push(
": `" + utils.stringifyValidator(validator, "") + "`"
);
} catch (ex) {
if (ex.code === "UNEXPECTED_VALIDATOR_TYPE") {
console.log(
@@ -148,12 +113,4 @@ Object.keys(types.BUILDER_KEYS)
readme.push("");
});
readme.push(
"",
"<!-- end generated section -->",
"",
readmeSrc.split("<!-- end generated section -->")[1].trim()
);
fs.writeFileSync(readmePath, readme.join("\n"));
// console.log(readme.join('\n'));
process.stdout.write(readme.join("\n"));

View File

@@ -1,12 +1,12 @@
"use strict";
const fs = require("fs");
const t = require("../packages/babel-types");
const t = require("../../packages/babel-types");
const utils = require("./utils");
const NODE_PREFIX = "BabelNode";
let code = `// NOTE: This file is autogenerated. Do not modify.
// See scripts/generate-interfaces.js for script used.
// See scripts/generators/flow.js for script used.
declare class ${NODE_PREFIX}Comment {
value: string;
@@ -73,29 +73,7 @@ for (const type in t.NODE_FIELDS) {
const validate = field.validate;
if (validate) {
if (validate.oneOf) {
typeAnnotation = validate.oneOf
.map(function(val) {
return JSON.stringify(val);
})
.join(" | ");
}
if (validate.type) {
typeAnnotation = validate.type;
if (typeAnnotation === "array") {
typeAnnotation = "Array<any>";
}
}
if (validate.oneOfNodeTypes) {
const types = validate.oneOfNodeTypes.map(
type => `${NODE_PREFIX}${type}`
);
typeAnnotation = types.join(" | ");
if (suffix === "?") typeAnnotation = "?" + typeAnnotation;
}
typeAnnotation = utils.stringifyValidator(validate, NODE_PREFIX);
}
if (typeAnnotation) {
@@ -116,7 +94,7 @@ for (const type in t.NODE_FIELDS) {
// Flow chokes on super() and import() :/
if (type !== "Super" && type !== "Import") {
lines.push(
`declare function ${type[0].toLowerCase() + type.slice(1)}(${args.join(
`declare function ${utils.toFunctionName(type)}(${args.join(
", "
)}): ${NODE_PREFIX}${type};`
);
@@ -171,4 +149,4 @@ code += `\ndeclare module "@babel/types" {
//
fs.writeFileSync(__dirname + "/../lib/types.js", code);
process.stdout.write(code);

View File

@@ -0,0 +1,188 @@
"use strict";
const t = require("../../packages/babel-types");
const utils = require("./utils");
let code = `// NOTE: This file is autogenerated. Do not modify.
// See scripts/generators/typescript.js for script used.
interface BaseComment {
value: string;
start: number;
end: number;
loc: SourceLocation;
type: "BlockComment" | "LineComment";
}
export interface BlockComment extends BaseComment {
type: "BlockComment";
}
export interface LineComment extends BaseComment {
type: "LineComment";
}
export type Comment = BlockComment | LineComment;
export interface SourceLocation {
start: {
line: number;
column: number;
};
end: {
line: number;
column: number;
};
}
interface BaseNode {
leadingComments: ReadonlyArray<Comment> | null;
innerComments: ReadonlyArray<Comment> | null;
trailingComments: ReadonlyArray<Comment> | null;
start: number | null;
end: number | null;
loc: SourceLocation | null;
type: Node["type"];
}
export type Node = ${t.TYPES.sort().join(" | ")};\n\n`;
//
const lines = [];
for (const type in t.NODE_FIELDS) {
const fields = t.NODE_FIELDS[type];
const fieldNames = sortFieldNames(Object.keys(t.NODE_FIELDS[type]), type);
const struct = ['type: "' + type + '";'];
const args = [];
fieldNames.forEach(fieldName => {
const field = fields[fieldName];
let typeAnnotation = utils.stringifyValidator(field.validate, "");
if (isNullable(field) && !hasDefault(field)) {
typeAnnotation += " | null";
}
if (areAllRemainingFieldsNullable(fieldName, fieldNames, fields)) {
args.push(
`${t.toBindingIdentifierName(fieldName)}${
isNullable(field) ? "?:" : ":"
} ${typeAnnotation}`
);
} else {
args.push(
`${t.toBindingIdentifierName(fieldName)}: ${typeAnnotation}${
isNullable(field) ? " | undefined" : ""
}`
);
}
if (t.isValidIdentifier(fieldName)) {
struct.push(`${fieldName}: ${typeAnnotation};`);
}
});
code += `export interface ${type} extends BaseNode {
${struct.join("\n ").trim()}
}\n\n`;
// super and import are reserved words in JavaScript
if (type !== "Super" && type !== "Import") {
lines.push(
`export function ${utils.toFunctionName(type)}(${args.join(
", "
)}): ${type};`
);
}
}
for (let i = 0; i < t.TYPES.length; i++) {
let decl = `export function is${
t.TYPES[i]
}(node: object, opts?: object | null): `;
if (t.NODE_FIELDS[t.TYPES[i]]) {
decl += `node is ${t.TYPES[i]};`;
} else {
decl += `boolean;`;
}
lines.push(decl);
}
lines.push(
`export function validate(n: Node, key: string, value: any): void;`,
`export function clone<T extends Node>(n: T): T;`,
`export function cloneDeep<T extends Node>(n: T): T;`,
`export function removeProperties(
n: Node,
opts?: { preserveComments: boolean } | null
): void;`,
`export function removePropertiesDeep<T extends Node>(
n: T,
opts?: { preserveComments: boolean } | null
): T;`,
`export type TraversalAncestors = ReadonlyArray<{
node: Node,
key: string,
index?: number,
}>;
export type TraversalHandler<T> = (node: Node, parent: TraversalAncestors, type: T) => void;
export type TraversalHandlers<T> = {
enter?: TraversalHandler<T>,
exit?: TraversalHandler<T>,
};`.replace(/(^|\n) {2}/g, "$1"),
// eslint-disable-next-line
`export function traverse<T>(n: Node, h: TraversalHandler<T> | TraversalHandlers<T>, state?: T): void;`
);
for (const type in t.DEPRECATED_KEYS) {
code += `/**
* @deprecated Use \`${t.DEPRECATED_KEYS[type]}\`
*/
export type ${type} = ${t.DEPRECATED_KEYS[type]};\n
`;
}
for (const type in t.FLIPPED_ALIAS_KEYS) {
const types = t.FLIPPED_ALIAS_KEYS[type];
code += `export type ${type} = ${types
.map(type => `${type}`)
.join(" | ")};\n`;
}
code += lines.join("\n") + "\n";
//
process.stdout.write(code);
//
function areAllRemainingFieldsNullable(fieldName, fieldNames, fields) {
const index = fieldNames.indexOf(fieldName);
return fieldNames.slice(index).every(_ => isNullable(fields[_]));
}
function hasDefault(field) {
return field.default != null;
}
function isNullable(field) {
return field.optional || hasDefault(field);
}
function sortFieldNames(fields, type) {
return fields.sort((fieldA, fieldB) => {
const indexA = t.BUILDER_KEYS[type].indexOf(fieldA);
const indexB = t.BUILDER_KEYS[type].indexOf(fieldB);
if (indexA === indexB) return fieldA < fieldB ? -1 : 1;
if (indexA === -1) return 1;
if (indexB === -1) return -1;
return indexA - indexB;
});
}

View File

@@ -0,0 +1,51 @@
exports.stringifyValidator = function stringifyValidator(
validator,
nodePrefix
) {
if (validator === undefined) {
return "any";
}
if (validator.each) {
return `Array<${stringifyValidator(validator.each, nodePrefix)}>`;
}
if (validator.chainOf) {
return stringifyValidator(validator.chainOf[1], nodePrefix);
}
if (validator.oneOf) {
return validator.oneOf.map(JSON.stringify).join(" | ");
}
if (validator.oneOfNodeTypes) {
return validator.oneOfNodeTypes.map(_ => nodePrefix + _).join(" | ");
}
if (validator.oneOfNodeOrValueTypes) {
return validator.oneOfNodeOrValueTypes
.map(_ => {
return isValueType(_) ? _ : nodePrefix + _;
})
.join(" | ");
}
if (validator.type) {
return validator.type;
}
return ["any"];
};
exports.toFunctionName = function toFunctionName(typeName) {
const _ = typeName.replace(/^TS/, "ts").replace(/^JSX/, "jsx");
return _.slice(0, 1).toLowerCase() + _.slice(1);
};
/**
* Heuristic to decide whether or not the given type is a value type (eg. "null")
* or a Node type (eg. "Expression").
*/
function isValueType(type) {
return type.charAt(0).toLowerCase() === type.charAt(0);
}