mirror of https://github.com/status-im/metro.git
Make collectDependencies smarter when evaluating the require argument
Reviewed By: BYK Differential Revision: D6592104 fbshipit-source-id: d90a93c51cb04e38cc172eb8eda932f64fce0075
This commit is contained in:
parent
57cfa19518
commit
3b497585f1
|
@ -1,5 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`throws on tagged template literals 1`] = `"Calls to require() expect exactly 1 string literal argument, but this was found: \`require(tag\`left-pad\`)\`."`;
|
||||
exports[`Evaluating static arguments throws on tagged template literals 1`] = `"Calls to require() expect exactly 1 string literal argument, but this was found: \`require(tag\`left-pad\`)\`."`;
|
||||
|
||||
exports[`throws on template literals with interpolations 1`] = `"Calls to require() expect exactly 1 string literal argument, but this was found: \`require(\`left\${\\"-\\"}pad\`)\`."`;
|
||||
exports[`Evaluating static arguments throws template literals with dyncamic interpolations 1`] = `"Calls to require() expect exactly 1 string literal argument, but this was found: \`require(\`left\${foo}pad\`)\`."`;
|
||||
|
||||
exports[`Evaluating static arguments throws when requiring non-strings 1`] = `"Calls to require() expect exactly 1 string literal argument, but this was found: \`require(1)\`."`;
|
||||
|
|
|
@ -101,17 +101,27 @@ it('collects mixed dependencies as being sync; reverse order', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('supports template literals as arguments', () => {
|
||||
describe('Evaluating static arguments', () => {
|
||||
it('supports template literals as arguments', () => {
|
||||
const ast = astFromCode('require(`left-pad`)');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
expect(dependencies).toEqual([{name: 'left-pad', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(`require(${dependencyMapName}[0], \`left-pad\`);`),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws on template literals with interpolations', () => {
|
||||
it('supports template literals with static interpolations', () => {
|
||||
const ast = astFromCode('require(`left${"-"}pad`)');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
expect(dependencies).toEqual([{name: 'left-pad', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(`require(${dependencyMapName}[0], \`left\${"-"}pad\`);`),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws template literals with dyncamic interpolations', () => {
|
||||
const ast = astFromCode('let foo;require(`left${foo}pad`)');
|
||||
try {
|
||||
collectDependencies(ast);
|
||||
throw new Error('should not reach');
|
||||
|
@ -119,9 +129,9 @@ it('throws on template literals with interpolations', () => {
|
|||
expect(error).toBeInstanceOf(InvalidRequireCallError);
|
||||
expect(error.message).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throws on tagged template literals', () => {
|
||||
it('throws on tagged template literals', () => {
|
||||
const ast = astFromCode('require(tag`left-pad`)');
|
||||
try {
|
||||
collectDependencies(ast);
|
||||
|
@ -130,6 +140,49 @@ it('throws on tagged template literals', () => {
|
|||
expect(error).toBeInstanceOf(InvalidRequireCallError);
|
||||
expect(error.message).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports multiple static strings concatenated', () => {
|
||||
const ast = astFromCode('require("foo_" + "bar")');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
expect(dependencies).toEqual([{name: 'foo_bar', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(`require(${dependencyMapName}[0], "foo_" + "bar");`),
|
||||
);
|
||||
});
|
||||
|
||||
it('supports concatenating strings and template literasl', () => {
|
||||
const ast = astFromCode('require("foo_" + "bar" + `_baz`)');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
expect(dependencies).toEqual([{name: 'foo_bar_baz', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(
|
||||
`require(${dependencyMapName}[0], "foo_" + "bar" + \`_baz\`);`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('supports using static variables in require statements', () => {
|
||||
const ast = astFromCode('const myVar="my";require("foo_" + myVar)');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
expect(dependencies).toEqual([{name: 'foo_my', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(
|
||||
`const myVar = \"my\"; require(${dependencyMapName}[0], "foo_" + myVar);`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when requiring non-strings', () => {
|
||||
const ast = astFromCode('require(1)');
|
||||
try {
|
||||
collectDependencies(ast);
|
||||
throw new Error('should not reach');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(InvalidRequireCallError);
|
||||
expect(error.message).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('exposes a string as `dependencyMapName` even without collecting dependencies', () => {
|
||||
|
|
|
@ -58,7 +58,7 @@ function collectDependencies(ast: Ast): CollectedDependencies {
|
|||
return;
|
||||
}
|
||||
if (isRequireCall(node.callee)) {
|
||||
const reqNode = processRequireCall(context, node, depMapIdent);
|
||||
const reqNode = processRequireCall(context, path, node, depMapIdent);
|
||||
visited.add(reqNode);
|
||||
}
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ function isRequireCall(callee) {
|
|||
}
|
||||
|
||||
function processImportCall(context, path, node, depMapIdent) {
|
||||
const [, name] = getModuleNameFromCallArgs('import', node);
|
||||
const [, name] = getModuleNameFromCallArgs('import', node, path);
|
||||
const index = assignDependencyIndex(context, name, 'import');
|
||||
const mapLookup = createDepMapLookup(depMapIdent, index);
|
||||
const newImport = makeAsyncRequire({
|
||||
|
@ -86,11 +86,15 @@ function processImportCall(context, path, node, depMapIdent) {
|
|||
path.replaceWith(newImport);
|
||||
}
|
||||
|
||||
function processRequireCall(context, node, depMapIdent) {
|
||||
const [nameLiteral, name] = getModuleNameFromCallArgs('require', node);
|
||||
function processRequireCall(context, path, node, depMapIdent) {
|
||||
const [nameExpression, name] = getModuleNameFromCallArgs(
|
||||
'require',
|
||||
node,
|
||||
path,
|
||||
);
|
||||
const index = assignDependencyIndex(context, name, 'require');
|
||||
const mapLookup = createDepMapLookup(depMapIdent, index);
|
||||
node.arguments = [mapLookup, nameLiteral];
|
||||
node.arguments = [mapLookup, nameExpression];
|
||||
return node;
|
||||
}
|
||||
|
||||
|
@ -98,21 +102,20 @@ function processRequireCall(context, node, depMapIdent) {
|
|||
* Extract the module name from `require` arguments. We support template
|
||||
* literal, for example one could write `require(`foo`)`.
|
||||
*/
|
||||
function getModuleNameFromCallArgs(type, node) {
|
||||
const args = node.arguments;
|
||||
if (args.length !== 1) {
|
||||
function getModuleNameFromCallArgs(type, node, path) {
|
||||
if (node.arguments.length !== 1) {
|
||||
throw invalidRequireOf(type, node);
|
||||
}
|
||||
const nameLiteral = args[0];
|
||||
if (nameLiteral.type === 'StringLiteral') {
|
||||
return [nameLiteral, nameLiteral.value];
|
||||
}
|
||||
if (nameLiteral.type === 'TemplateLiteral') {
|
||||
if (nameLiteral.quasis.length !== 1) {
|
||||
throw invalidRequireOf(type, node);
|
||||
}
|
||||
return [nameLiteral, nameLiteral.quasis[0].value.cooked];
|
||||
|
||||
const nameExpression = node.arguments[0];
|
||||
|
||||
// Try to evaluate the first argument of the require() statement.
|
||||
// If it can be statically evaluated, resolve it.
|
||||
const result = path.get('arguments.0').evaluate();
|
||||
if (result.confident && typeof result.value === 'string') {
|
||||
return [nameExpression, result.value];
|
||||
}
|
||||
|
||||
throw invalidRequireOf(type, node);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue