flow2schema/src/collector/declarations.js

240 lines
6.3 KiB
JavaScript
Raw Normal View History

2018-07-25 08:38:10 +03:00
// @flow
2017-11-28 16:35:28 +03:00
import wu from 'wu';
// @see flow#5376.
2017-11-21 15:28:59 +03:00
import type {
Block, ClassDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, Identifier,
ImportDeclaration, ImportDefaultSpecifier, ImportSpecifier, InterfaceDeclaration,
Node, TypeAlias, TypeParameterDeclaration, VariableDeclaration, VariableDeclarator,
DeclareTypeAlias, DeclareInterface, DeclareClass,
2017-11-21 15:28:59 +03:00
} from '@babel/types';
import {
isCallExpression, isClassDeclaration, isClassMethod, isExportDefaultDeclaration, isProgram,
2017-11-21 15:28:59 +03:00
isExportNamedDeclaration, isIdentifier, isImportDeclaration, isImportNamespaceSpecifier,
isImportSpecifier, isInterfaceDeclaration, isObjectPattern, isObjectProperty, isDeclareClass,
isStringLiteral, isTypeAlias, isVariableDeclaration, isDeclareTypeAlias, isDeclareInterface,
2017-11-21 15:28:59 +03:00
} from '@babel/types';
2017-12-01 22:39:03 +03:00
import {invariant} from '../utils';
2017-11-21 15:28:59 +03:00
import Context from './context';
import type {ExternalInfo, TemplateParam} from './query';
/*
* Blocks.
*/
function processBlock(ctx: Context, node: Block) {
const scoped = isProgram(node);
if (!scoped) {
ctx.enter();
}
2017-11-21 15:28:59 +03:00
for (const entry of node.body) {
processBodyEntry(ctx, entry);
}
2017-11-21 15:28:59 +03:00
if (!scoped) {
ctx.exit();
}
}
2017-11-21 15:28:59 +03:00
function processBodyEntry(ctx: Context, node: Node) {
if (isDeclaration(node)) {
processDeclaration(ctx, node);
return;
}
2017-11-21 15:28:59 +03:00
if (isImportDeclaration(node)) {
processImportDeclaration(ctx, node);
return;
}
2017-11-21 15:28:59 +03:00
if (isExportNamedDeclaration(node)) {
processExportNamedDeclaration(ctx, node);
return;
}
2017-11-21 15:28:59 +03:00
if (isExportDefaultDeclaration(node)) {
processExportDefaultDeclaration(ctx, node);
return;
}
2017-11-21 15:28:59 +03:00
if (isVariableDeclaration(node)) {
processVariableDeclaration(ctx, node);
return;
}
2017-11-21 15:28:59 +03:00
// TODO: do it only for "all"-mode.
ctx.freestyle(node);
}
2017-11-21 15:28:59 +03:00
/*
* Imports.
*
* TODO: warning about "import typeof".
* TODO: support form "import *".
* TODO: support complex patterns.
*/
2017-11-21 15:28:59 +03:00
function processImportDeclaration(ctx: Context, node: ImportDeclaration) {
const path = node.source.value;
2017-11-21 15:28:59 +03:00
for (const specifier of node.specifiers) {
if (isImportNamespaceSpecifier(specifier)) {
continue;
}
2017-11-21 15:28:59 +03:00
ctx.external(extractExternal(specifier, path));
}
}
2017-11-21 15:28:59 +03:00
function extractExternal(node: ImportSpecifier | ImportDefaultSpecifier, path: string): ExternalInfo {
return {
local: node.local.name,
imported: isImportSpecifier(node) ? node.imported.name : null,
path,
};
}
2017-11-21 15:28:59 +03:00
function processVariableDeclaration(ctx: Context, node: VariableDeclaration) {
for (const declarator of node.declarations) {
processVariableDeclarator(ctx, declarator);
}
}
2017-11-21 15:28:59 +03:00
function processVariableDeclarator(ctx: Context, node: VariableDeclarator) {
const path = extractRequire(node.init);
2017-11-21 15:28:59 +03:00
if (path == null) {
return null;
}
2017-11-21 15:28:59 +03:00
const {id} = node;
2017-11-21 15:28:59 +03:00
if (isIdentifier(id)) {
const external = extractCommonjsDefaultExternal(id, path);
2017-11-21 15:28:59 +03:00
ctx.external(external);
} else if (isObjectPattern(id)) {
const externals = extractCommonjsNamedExternals(id.properties, path);
2017-11-21 15:28:59 +03:00
externals.forEach(external => ctx.external(external));
}
}
2017-11-21 15:28:59 +03:00
function extractRequire(node: Node): ?string {
return isCallExpression(node)
&& isIdentifier(node.callee, {name: 'require'})
&& node.arguments.length > 0
// TODO: warning about dynamic imports.
&& isStringLiteral(node.arguments[0])
? node.arguments[0].value : null;
}
2017-11-21 15:28:59 +03:00
function extractCommonjsDefaultExternal(node: Identifier, path: string): ExternalInfo {
return {
local: node.name,
imported: null,
path,
};
}
2017-11-21 15:28:59 +03:00
function extractCommonjsNamedExternals<+T: Node>(nodes: T[], path: string): ExternalInfo[] {
const pred = (n): %checks => isObjectProperty(n) && isIdentifier(n.key) && isIdentifier(n.value);
2017-11-21 15:28:59 +03:00
const make = prop => ({
local: (prop: $FlowFixMe).value.name,
imported: (prop: $FlowFixMe).key.name,
path,
});
2017-11-28 16:35:28 +03:00
return wu(nodes)
.filter(pred)
.map(make)
.toArray();
2017-11-21 15:28:59 +03:00
}
2017-11-21 15:28:59 +03:00
/*
* Exports.
*
* TODO: support "export from" form.
* TODO: support commonjs.
*/
2017-11-21 15:28:59 +03:00
function processExportNamedDeclaration(ctx: Context, node: ExportNamedDeclaration) {
if (isDeclaration(node.declaration)) {
2019-10-21 20:00:31 +03:00
node.declaration.leadingComments = node.leadingComments;
2017-11-21 15:28:59 +03:00
const reference = processDeclaration(ctx, node.declaration);
ctx.provide(reference, reference);
}
2017-11-21 15:28:59 +03:00
for (const specifier of node.specifiers) {
const reference = specifier.local.name;
const exported = specifier.exported.name;
const name = exported === 'default' ? null : exported;
2017-11-21 15:28:59 +03:00
ctx.provide(name, reference);
}
}
2017-11-21 15:28:59 +03:00
function processExportDefaultDeclaration(ctx: Context, node: ExportDefaultDeclaration) {
if (!isDeclaration(node.declaration)) {
return;
}
2017-11-21 15:28:59 +03:00
const reference = processDeclaration(ctx, node.declaration);
2017-11-21 15:28:59 +03:00
ctx.provide(null, reference);
}
2017-11-21 15:28:59 +03:00
/*
* Declarations.
*
* TODO: support defaults in generics.
* TODO: support "declare ..." form.
*/
type Declaration = TypeAlias | InterfaceDeclaration | ClassDeclaration
| DeclareTypeAlias | DeclareInterface | DeclareClass;
2017-11-21 15:28:59 +03:00
function isDeclaration(node: mixed): boolean %checks {
return isTypeAlias(node) || isInterfaceDeclaration(node) || isClassDeclaration(node)
|| isDeclareTypeAlias(node) || isDeclareInterface(node) || isDeclareClass(node);
2017-11-21 15:28:59 +03:00
}
2017-11-21 15:28:59 +03:00
function processDeclaration(ctx: Context, node: Declaration) {
const {name} = node.id;
const params = node.typeParameters && extractTemplateParams(node.typeParameters);
2017-11-21 15:28:59 +03:00
// TODO: do it only for "all"-mode.
if (isClassDeclaration(node)) {
2017-11-28 16:35:28 +03:00
const methods = wu(node.body.body).filter(isClassMethod).toArray();
2017-11-21 15:28:59 +03:00
for (const method of methods) {
ctx.freestyle(method);
}
}
2017-11-21 15:28:59 +03:00
ctx.declare(name, node, params);
2017-11-21 15:28:59 +03:00
return name;
}
2017-11-21 15:28:59 +03:00
function extractTemplateParams(node: TypeParameterDeclaration): TemplateParam[] {
2017-11-28 16:35:28 +03:00
return wu(node.params)
.map(param => ({
name: param.name,
// TODO: default params.
value: null,
}))
.toArray();
}
2017-11-21 15:28:59 +03:00
export default {
Program: processBlock,
BlockStatement: processBlock,
};