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
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* @flow */

import {equal, ok} from 'assert';
import {fs} from '../util';
import generate from 'babel-generator';
import {format} from 'prettier';
import os from 'os';
import path from 'path';

import buildProgram from '../buildProgram';
import crawlTypeDefinitions from '../crawlTypeDefinitions';
import crawlTypeDependencies from '../crawlTypeDependencies';

const expected = `
import t from "flow-runtime";
t.declare(
t.module("fixture-package", t => {
t.declare(t.type("ExternalUser", t.object(t.property("name", t.string()))));
})
);
`;

describe('JS Flow Package Definitions', () => {
it('uses package index.js.flow definitions for imported package types', async () => {
const workspace = await createPackageFixture();
try {
const dependencies = await crawlTypeDependencies([
path.join(workspace, 'src')
]);
const definitions = await crawlTypeDefinitions([
path.join(workspace, 'node_modules')
]);

ok(definitions.get('fixture-package', 'ExternalUser'));

const code = format(generate(buildProgram(null, dependencies, definitions)).code);
equal(code.trim(), expected.trim());
}
finally {
await fs.unlinkAsync(path.join(workspace, 'node_modules/fixture-package/index.js'));
await fs.unlinkAsync(path.join(workspace, 'node_modules/fixture-package/index.js.flow'));
await fs.rmdirAsync(path.join(workspace, 'node_modules/fixture-package'));
await fs.rmdirAsync(path.join(workspace, 'node_modules'));
await fs.unlinkAsync(path.join(workspace, 'src/consumer.js'));
await fs.rmdirAsync(path.join(workspace, 'src'));
await fs.rmdirAsync(workspace);
}
});
});

async function createPackageFixture () {
const workspace = await fs.mkdtempAsync(path.join(os.tmpdir(), 'flow-runtime-js-flow-'));
const packageDir = path.join(workspace, 'node_modules/fixture-package');
const sourceDir = path.join(workspace, 'src');

await fs.mkdirAsync(path.join(workspace, 'node_modules'));
await fs.mkdirAsync(packageDir);
await fs.mkdirAsync(sourceDir);
await fs.writeFileAsync(path.join(packageDir, 'index.js'), 'exports.value = true;');
await fs.writeFileAsync(path.join(packageDir, 'index.js.flow'), `/* @flow */

export type ExternalUser = {
name: string
};
`);
await fs.writeFileAsync(path.join(sourceDir, 'consumer.js'), `/* @flow */

import type {ExternalUser} from 'fixture-package';

type LocalUser = {
user: ExternalUser
};
`);

return workspace;
}
43 changes: 41 additions & 2 deletions packages/flow-runtime-cli/src/findFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,47 @@ async function collectFiles (fileOrDir: string, collected: string[]): Promise<st
await collectFiles(path.join(fileOrDir, item), collected);
}
}
else if (collected.indexOf(fileOrDir) === -1 && /\.js(x|m)?$/.test(fileOrDir)) {
collected.push(fileOrDir);
else if (isJavaScriptFile(fileOrDir)) {
await addJavaScriptFile(fileOrDir, collected);
}
return collected;
}

async function addJavaScriptFile (filename: string, collected: string[]) {
if (isFlowDefinitionFile(filename)) {
const runtimeFile = filename.replace(/\.flow$/, '');
const runtimeIndex = collected.indexOf(runtimeFile);
if (runtimeIndex !== -1) {
collected.splice(runtimeIndex, 1);
}
if (collected.indexOf(filename) === -1) {
collected.push(filename);
}
return;
}

if (await hasFlowDefinitionFile(filename)) {
return;
}
else if (collected.indexOf(filename) === -1) {
collected.push(filename);
}
}

function isJavaScriptFile (filename: string): boolean {
return /\.js(x|m)?$/.test(filename) || isFlowDefinitionFile(filename);
}

function isFlowDefinitionFile (filename: string): boolean {
return /\.js\.flow$/.test(filename);
}

async function hasFlowDefinitionFile (filename: string): Promise<boolean> {
try {
await fs.statAsync(`${filename}.flow`);
return true;
}
catch (e) {
return false;
}
}
47 changes: 46 additions & 1 deletion packages/flow-runtime-cli/src/importAST.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,26 @@ import {findIdentifiers, getTypeParameters} from 'babel-plugin-flow-runtime';
import shouldIgnoreType from './shouldIgnoreType';

import type {NodePath, Scope} from '@babel/traverse';
import type {FlowModule, FlowEntity} from './Graph';
import {FlowModule} from './Graph';
import type {FlowEntity} from './Graph';

type Node = {
type: string;
filename?: string;
program?: {
body: Object[];
};
};


export default function importAST (graph: FlowModule, file: Node) {
const nodeTypeParameters = new WeakMap();
const implicitModuleName = getImplicitModuleName(file);

const moduleStack = [graph];
if (implicitModuleName && !hasExplicitDeclaredModule(file)) {
moduleStack.push(getOrCreateModule(graph, implicitModuleName));
}

function currentModule (): FlowModule {
return moduleStack[moduleStack.length - 1];
Expand Down Expand Up @@ -108,6 +117,12 @@ export default function importAST (graph: FlowModule, file: Node) {
}
},
ExportNamedDeclaration (path: NodePath) {
if (implicitModuleName && path.has('declaration')) {
const declaration = path.get('declaration');
if (declaration.isTypeAlias()) {
declaration.node.type = 'DeclareTypeAlias';
}
}
if (path.node.declare) {
// this is not a real export, it's a DeclareExportDeclaration
// which babylon doesn't support
Expand Down Expand Up @@ -245,6 +260,36 @@ export default function importAST (graph: FlowModule, file: Node) {
return graph;
}

function getImplicitModuleName (file: Node): ?string {
const {filename} = file;
if (!filename) {
return;
}
const normalized = filename.replace(/\\/g, '/');
const match = /\/node_modules\/((?:@[^/]+\/)?[^/]+)\/index\.js\.flow$/.exec(normalized);
return match && match[1];
}

function hasExplicitDeclaredModule (file: Node): boolean {
const program = file.program;
if (!program) {
return false;
}
return program.body.some(item => item.type === 'DeclareModule');
}

function getOrCreateModule (graph: FlowModule, name: string): FlowModule {
const existing = graph.modules[name];
if (existing) {
return existing;
}
const child = new FlowModule();
child.name = name;
child.parent = graph;
graph.modules[name] = child;
return child;
}

function findContainingPath (path: NodePath): ? NodePath {
let child = path;
let parent = path.parentPath;
Expand Down