Skip to content

Commit

Permalink
Merge c0db039 into e24a73b
Browse files Browse the repository at this point in the history
  • Loading branch information
re-gor committed Dec 11, 2018
2 parents e24a73b + c0db039 commit dd92889
Show file tree
Hide file tree
Showing 19 changed files with 691 additions and 20 deletions.
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

0 comments on commit dd92889

Please sign in to comment.