Skip to content

Commit d924f16

Browse files
phateddanez
authored andcommitted
Add factory around importer that takes args, rename to makeFsImporter
1 parent ab46f33 commit d924f16

File tree

4 files changed

+153
-132
lines changed

4 files changed

+153
-132
lines changed

src/__tests__/main-test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ describe('main', () => {
216216
});
217217
});
218218

219-
// Fixures uses the resolveImports importer even though it is not the default
219+
// Fixures uses the filesystem importer for easier e2e tests
220+
// even though it is not the default
220221
describe('fixtures', () => {
221222
const fixturePath = path.join(__dirname, 'fixtures');
222223
const fileNames = fs.readdirSync(fixturePath);
@@ -228,7 +229,7 @@ describe('main', () => {
228229
let result;
229230
expect(() => {
230231
result = parse(fileContent, null, null, {
231-
importer: importers.resolveImports,
232+
importer: importers.makeFsImporter(),
232233
filename: filePath,
233234
babelrc: false,
234235
});

src/importer/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
*/
99

1010
import ignoreImports from './ignoreImports';
11-
import resolveImports from './resolveImports';
11+
import makeFsImporter from './makeFsImporter';
1212

13-
export { ignoreImports, resolveImports };
13+
export { ignoreImports, makeFsImporter };

src/importer/makeFsImporter.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import { namedTypes as t, NodePath } from 'ast-types';
11+
import { traverseShallow } from '../utils/traverse';
12+
import resolve from 'resolve';
13+
import { dirname } from 'path';
14+
import buildParser, { type Options } from '../babelParser';
15+
import fs from 'fs';
16+
17+
function defaultLookupModule(filename, basedir) {
18+
return resolve.sync(filename, {
19+
basedir,
20+
extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx'],
21+
});
22+
}
23+
24+
// Factory for the resolveImports importer
25+
export default function makeFsImporter(
26+
lookupModule: (
27+
filename: string,
28+
basedir: string,
29+
) => string = defaultLookupModule,
30+
cache: Map<string, NodePath> = new Map(),
31+
) {
32+
return resolveImportedValue;
33+
34+
function resolveImportedValue(
35+
path: NodePath,
36+
name: string,
37+
seen: Set<string> = new Set(),
38+
) {
39+
// Bail if no filename was provided for the current source file.
40+
// Also never traverse into react itself.
41+
const source = path.node.source.value;
42+
const options = getOptions(path);
43+
if (!options || !options.filename || source === 'react') {
44+
return null;
45+
}
46+
47+
// Resolve the imported module using the Node resolver
48+
const basedir = dirname(options.filename);
49+
let resolvedSource;
50+
51+
try {
52+
resolvedSource = lookupModule(source, basedir);
53+
} catch (err) {
54+
return null;
55+
}
56+
57+
// Prevent recursive imports
58+
if (seen.has(resolvedSource)) {
59+
return null;
60+
}
61+
62+
seen.add(resolvedSource);
63+
64+
let nextPath = cache.get(resolvedSource);
65+
if (!nextPath) {
66+
// Read and parse the code
67+
const src = fs.readFileSync(resolvedSource, 'utf8');
68+
const parseOptions: Options = {
69+
...options,
70+
parserOptions: {},
71+
filename: resolvedSource,
72+
};
73+
74+
const parser = buildParser(parseOptions);
75+
const ast = parser.parse(src);
76+
ast.__src = src;
77+
nextPath = new NodePath(ast).get('program');
78+
cache.set(resolvedSource, nextPath);
79+
}
80+
81+
return findExportedValue(nextPath, name, seen);
82+
}
83+
84+
// Find the root Program node, which we attached our options too in babelParser.js
85+
function getOptions(path: NodePath): Options {
86+
while (!t.Program.check(path.node)) {
87+
path = path.parentPath;
88+
}
89+
90+
return path.node.options || {};
91+
}
92+
93+
// Traverses the program looking for an export that matches the requested name
94+
function findExportedValue(programPath, name, seen) {
95+
let resultPath: ?NodePath = null;
96+
97+
traverseShallow(programPath, {
98+
visitExportNamedDeclaration(path: NodePath) {
99+
const { declaration, specifiers, source } = path.node;
100+
if (declaration && declaration.id && declaration.id.name === name) {
101+
resultPath = path.get('declaration');
102+
} else if (declaration && declaration.declarations) {
103+
path.get('declaration', 'declarations').each((declPath: NodePath) => {
104+
const decl = declPath.node;
105+
// TODO: ArrayPattern and ObjectPattern
106+
if (
107+
t.Identifier.check(decl.id) &&
108+
decl.id.name === name &&
109+
decl.init
110+
) {
111+
resultPath = declPath.get('init');
112+
}
113+
});
114+
} else if (specifiers) {
115+
path.get('specifiers').each((specifierPath: NodePath) => {
116+
if (specifierPath.node.exported.name === name) {
117+
if (source) {
118+
const local = specifierPath.node.local.name;
119+
resultPath = resolveImportedValue(path, local, seen);
120+
} else {
121+
resultPath = specifierPath.get('local');
122+
}
123+
}
124+
});
125+
}
126+
127+
return false;
128+
},
129+
visitExportDefaultDeclaration(path: NodePath) {
130+
if (name === 'default') {
131+
resultPath = path.get('declaration');
132+
}
133+
134+
return false;
135+
},
136+
visitExportAllDeclaration(path: NodePath) {
137+
const resolvedPath = resolveImportedValue(path, name, seen);
138+
if (resolvedPath) {
139+
resultPath = resolvedPath;
140+
}
141+
142+
return false;
143+
},
144+
});
145+
146+
return resultPath;
147+
}
148+
}

src/importer/resolveImports.js

Lines changed: 0 additions & 128 deletions
This file was deleted.

0 commit comments

Comments
 (0)