Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions src/collector/declarations.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import type {
Block, ClassDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, Identifier,
ImportDeclaration, ImportDefaultSpecifier, ImportSpecifier, InterfaceDeclaration,
Node, TypeAlias, TypeParameterDeclaration, VariableDeclaration, VariableDeclarator,
DeclareTypeAlias, DeclareInterface, DeclareClass,
DeclareTypeAlias, DeclareInterface, DeclareClass
} from '@babel/types';

import {
isCallExpression, isClassDeclaration, isClassMethod, isExportDefaultDeclaration, isProgram,
isExportNamedDeclaration, isIdentifier, isImportDeclaration, isImportNamespaceSpecifier,
isImportSpecifier, isInterfaceDeclaration, isObjectPattern, isObjectProperty, isDeclareClass,
isStringLiteral, isTypeAlias, isVariableDeclaration, isDeclareTypeAlias, isDeclareInterface,
isVariableDeclarator
} from '@babel/types';

import {invariant} from '../utils';
Expand Down Expand Up @@ -106,7 +107,23 @@ function processVariableDeclaration(ctx: Context, node: VariableDeclaration) {
}

function processVariableDeclarator(ctx: Context, node: VariableDeclarator) {
const path = extractRequire(node.init);
if (isRequireExpression(node.init)) {
extractExternals(ctx, node);
} else if (isIdentifier(node.id)) {
extractIdentifier(ctx, node);
}
}

function extractIdentifier(ctx: Context, node: VariableDeclarator) {
const {id} = node;

invariant(isIdentifier(id));

ctx.declare(id.name, node);
}

function extractExternals(ctx: Context, node: VariableDeclarator) {
const path = extractRequirePath(node.init);

if (path == null) {
return null;
Expand All @@ -125,13 +142,23 @@ function processVariableDeclarator(ctx: Context, node: VariableDeclarator) {
}
}

function extractRequire(node: Node): ?string {
function extractRequirePath(node: Node): ?string {
if (
// TODO: flow fails but should not
isRequireExpression(node) && isStringLiteral(node.arguments[0])
) {
return String(node.arguments[0].value);
}

return null;
}

function isRequireExpression(node: Node): boolean %checks {
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;
}

function extractCommonjsDefaultExternal(node: Identifier, path: string): ExternalInfo {
Expand Down Expand Up @@ -162,13 +189,29 @@ function extractCommonjsNamedExternals<+T: Node>(nodes: T[], path: string): Exte
*
* TODO: support "export from" form.
* TODO: support commonjs.
* TODO: implement ObjectPattern, ArrayPattern, RestElement for variable declaration
*/

function processExportNamedDeclaration(ctx: Context, node: ExportNamedDeclaration) {
if (isDeclaration(node.declaration)) {
const reference = processDeclaration(ctx, node.declaration);

ctx.provide(reference, reference);
} else if (isVariableDeclaration(node.declaration)) {
const {declaration} = node;
processVariableDeclaration(ctx, declaration);
const {declarations} = declaration;

if (Array.isArray(declarations)) {
for (const declarator of declarations) {
const {id} = declarator;

if (!isVariableDeclarator(declarator) || !isIdentifier(id)) {
continue;
}

ctx.provide(id.name, id.name);
}
}
}

for (const specifier of node.specifiers) {
Expand Down
8 changes: 7 additions & 1 deletion src/collector/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function processClassDeclaration(ctx: Context, node: ClassDeclaration) {
ctx.define(name, intersection);
}

function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type {
export function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type {
// TODO: ThisTypeAnnotation
// TODO: VoidTypeAnnotation
// TODO: TypeofTypeAnnotation
Expand Down Expand Up @@ -134,6 +134,12 @@ function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type {
return t.createAny();
case 'MixedTypeAnnotation':
return t.createMixed();
case 'TypeofTypeAnnotation':
if (!node.argument || node.argument.type !== 'GenericTypeAnnotation') {
return t.createAny();
}

return makeType(ctx, node.argument);
case 'FunctionTypeAnnotation':
return null;
default:
Expand Down
16 changes: 8 additions & 8 deletions src/collector/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ function unwrap(params: (?Type)[]): ?Type {
function keys(params: (?Type)[], resolve: TypeId => Type): ?Type {
invariant(params.length === 1);

const [ref] = params;

invariant(ref && ref.kind === 'reference');
const [param] = params;
invariant(param);
invariant(param.kind === 'reference' || param.kind === 'record');

const record = resolve(ref.to);
const record = param.kind === 'reference' ? resolve(param.to) : param;

invariant(record.kind === 'record');

Expand All @@ -127,11 +127,11 @@ function keys(params: (?Type)[], resolve: TypeId => Type): ?Type {
function values(params: (?Type)[], resolve: TypeId => Type): ?Type {
invariant(params.length === 1);

const [ref] = params;

invariant(ref && ref.kind === 'reference');
const [param] = params;
invariant(param);
invariant(param.kind === 'reference' || param.kind === 'record');

const record = resolve(ref.to);
const record = param.kind === 'reference' ? resolve(param.to) : param;

invariant(record.kind === 'record');

Expand Down
3 changes: 2 additions & 1 deletion src/collector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import traverse from './traverse';
import globals from './globals';
import definitionGroup from './definitions';
import declarationGroup from './declarations';
import variableGroup from './variables';
import Fund from '../fund';
import Module from './module';
import Scope from './scope';
Expand All @@ -19,7 +20,7 @@ import type Parser from '../parser';
import type {Type, TypeId} from '../types';
import type {TemplateParam} from './query';

const VISITOR = Object.assign({}, definitionGroup, declarationGroup);
const VISITOR = Object.assign({}, definitionGroup, declarationGroup, variableGroup);

export default class Collector {
+root: string;
Expand Down
215 changes: 215 additions & 0 deletions src/collector/variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// @flow

import type {
Identifier, FlowTypeAnnotation,
UpdateExpression, UnaryExpression, StringLiteral, SequenceExpression,
ObjectExpression, NumericLiteral, NullLiteral, NewExpression,
BooleanLiteral, ArrayExpression, VariableDeclarator,
ObjectProperty, SpreadProperty, SpreadElement
} from '@babel/types';

import type {
Type, RecordType, Field, ArrayType, TupleType, MapType, MaybeType
} from '../types';

import {
Node,
isUpdateExpression,isUnaryExpression, isStringLiteral, isSequenceExpression,
isObjectExpression, isNumericLiteral, isNullLiteral, isNewExpression,
isBooleanLiteral, isArrayExpression, isIdentifier, isTypeAnnotation,
isObjectProperty, isSpreadProperty, isSpreadElement
} from '@babel/types';

import * as t from '../types';
import {invariant, uniqLastBy} from '../utils';
import Context from './context';
import {makeType as makeTypeFromTypeAnnotation} from './definitions';

// TODO MetaProperty, MemberExpression, LogicalExpression, FunctionExpression,
// ConditionalExpression, CallExpression, ArrowFunctionExpression
type AvailableExpression =
UpdateExpression | UnaryExpression | StringLiteral | SequenceExpression |
ObjectExpression | NumericLiteral | NullLiteral | NewExpression |
BooleanLiteral | ArrayExpression | Identifier;

function processVariableDeclarator (ctx: Context, node: VariableDeclarator) {
const {id, init} = node;

if (!isIdentifier(id)) {
return;
}

if (isTypeAnnotation(id.typeAnnotation)) {
processTypeFromTypeAnnotation(ctx, id)
} else if (isAvailableValueExpression(init)) {
processTypeFromInit(ctx, id, init);
}
}

function processTypeFromTypeAnnotation(ctx: Context, id: Identifier) {
invariant(id.typeAnnotation);

const {name, typeAnnotation} = id;
const type = makeTypeFromTypeAnnotation(ctx, typeAnnotation);

// TODO: support function variables.
invariant(type);

ctx.define(name, type);
}

function isAvailableValueExpression(value: Node): boolean %checks {
return isUpdateExpression(value) || isUnaryExpression(value) || isStringLiteral(value) || isSequenceExpression(value) ||
isObjectExpression(value) || isNumericLiteral(value) || isNullLiteral(value) || isNewExpression(value) ||
isBooleanLiteral(value) || isArrayExpression(value) || isIdentifier(value);
}

function processTypeFromInit(ctx: Context, id: Identifier, init: AvailableExpression) {
const type = makeTypeFromValue(ctx, init);
invariant(type);
ctx.define(id.name, type);
}

function makeTypeFromValue(ctx: Context, node: AvailableExpression) {
switch (node.type) {
// case init === null;
case 'Identifier':
if (node.name === 'undefined') {
// TODO Fails in json generator
// type = t.createLiteral(undefined);
return t.createAny();
} else {
return makeReference(ctx, node.name);
}
case 'BooleanLiteral':
return t.createBoolean();
case 'StringLiteral':
return t.createString();
case 'NumericLiteral':
case 'UpdateExpression':
return t.createNumber('f64');
case 'NullLiteral':
return t.createLiteral(null);
case 'ObjectExpression':
return createTypeFromObjectExpression(ctx, node);
case 'ArrayExpression':
return createTypeFromArrayExpression(ctx, node);
case 'SequenceExpression':
return createTypeFromSequenceExpression(ctx, node);
// TODO
case 'UnaryExpression':
case 'NewExpression':
default:
return t.createAny();
}
}

function createTypeFromObjectExpression(ctx: Context, value: ObjectExpression): ?RecordType {
const {properties} = value;

const fields = properties
.reduce((acc, prop) => {
if (isObjectProperty(prop)) {
acc.push(makeFieldFromObjectProperty(ctx, prop));

return acc;
}

if (isSpreadElement(prop) || isSpreadProperty(prop)) {
acc.push(...obtainFieldsFromSpreadProperty(ctx, prop));

return acc;
}

return acc;
}, [])
.filter(Boolean);

return t.createRecord(uniqLastBy(fields, f => f.name))
}

function makeFieldFromObjectProperty(ctx: Context, node: ObjectProperty): ?Field {
if (!(isStringLiteral(node.key) || isIdentifier(node.key) || isNumericLiteral(node.key))) {
return null;
}

const name = isIdentifier(node.key) ? node.key.name : String(node.key.value);
const value = isAvailableValueExpression(node.value) ? makeTypeFromValue(ctx, node.value) : null;

if (!value) {
return null;
}

return {
name,
value,
required: true
};
}

function obtainFieldsFromSpreadProperty(ctx: Context, node: SpreadProperty | SpreadElement): Field[] {
if (isObjectExpression(node.argument)) {
const type = createTypeFromObjectExpression(ctx, node.argument);

return type ? type.fields : [];
}

if (isIdentifier(node.argument)) {
const type = ctx.query(node.argument.name);
invariant(type);

// TODO: ArrayExpression
if (type.kind !== 'record') {
return [];
}

// TODO improve typing of t.clone
// flowlint-next-line unclear-type:off
return ((t.clone(type): any): RecordType).fields;
}

// TODO: ArrayExpression, SequenceExpression
return [];
}

function createTypeFromArrayExpression(ctx: Context, node: ArrayExpression): ArrayType {
const types: Type[] = node.elements.map(element => {
if (!isAvailableValueExpression(element)) {
return null;
}

return makeTypeFromValue(ctx, element);
}).filter(Boolean);

return t.createArray(t.createUnion(types));
}

function createTypeFromSequenceExpression(ctx: Context, node: SequenceExpression): ?Type {
const {expressions} = node;

if (!Array.isArray(expressions) || !expressions.length) {
return null;
}

const last = expressions[expressions.length - 1];

return isAvailableValueExpression(last) ? makeTypeFromValue(ctx, last) : null;
}

function makeReference(ctx: Context, name: string): ?Type {
const type = ctx.query(name);

if (!type) {
return null;
}

if (!type.id) {
return t.clone(type);
}

return t.createReference(t.clone(type.id));
}

export default {
VariableDeclarator: processVariableDeclarator,
}
2 changes: 1 addition & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default class Parser {
allowSuperOutsideMethod: true,
sourceType: 'module',
// TODO: review other plugins.
plugins: ['*', 'jsx', 'flow', 'classProperties'],
plugins: ['*', 'jsx', 'flow', 'classProperties', 'objectRestSpread'],
});
}
}
Loading