/* * Converts the given angular injected parameter into an explicit require statement * * Run this with jscodeshift * @example * jscodeshift . --specifier='Auth' --source='application/Auth' * * Live demo: https://astexplorer.net/#/gist/5492d2b9850a451d8e8d532bc64f21ce/latest * * Converts: * app.controller('myController', function ($scope, Auth, $q) { * }); * * function test($scope, Auth, $q) { * 'ngInject'; * } * * app.component('myComponent', { * controller: function ($scope, Auth, $q) { * } * }); * * app.directive('myDirective', function (Auth) { * return { * controller: function ($scope, Auth, $q) { * } * }; * }); * * to: * import Auth from 'application/Auth'; * app.controller('myController', function ($scope, $q) { * }); * * function test($scope, $q) { * 'ngInject'; * } * * app.component('myComponent', { * controller: function ($scope, $q) { * } * }); * * app.directive('myDirective', function () { * return { * controller: function ($scope, $q) { * } * }; * }); */ const angularMethods = [ 'config', 'provider', 'factory', 'service', 'controller', 'filter', 'directive' ]; function isNgInject(functionExpression) { if (!functionExpression.body.body.length) { return false; } let firstBodyNode = functionExpression.body.body[0]; return ( firstBodyNode.type === 'ExpressionStatement' && firstBodyNode.expression.type === 'Literal' && firstBodyNode.expression.value === 'ngInject' ); } function isControllerOnObject(path) { return ( path.name === 'value' && path.parentPath.parentPath.name === 'properties' && path.node.type === 'FunctionExpression' && path.parentPath.node.type === 'Property' && path.parentPath.parentPath.node.type === 'ObjectExpression' ); } function isParameterOfAngularMethod(path) { return ( path.node.type === 'FunctionExpression' && path.parentPath.name === 'arguments' && path.parentPath.parentPath.node.type === 'CallExpression' && path.parentPath.parentPath.node.callee.type === 'MemberExpression' && path.parentPath.parentPath.node.callee.property.type === 'Identifier' && angularMethods.indexOf(path.parentPath.parentPath.node.callee.property.name) > -1 ); } function isUseStrict(node) { return ( node && node.type === 'ExpressionStatement' && node.expression.type === 'Literal' && node.expression.value === 'use strict' ); } module.exports = function transformer(file, api, options) { const j = api.jscodeshift; const { specifier = 'Auth', source = 'application/Auth' } = options; let addedImport = false; let output = j(file.source) .find(j.Identifier) .filter((path) => { return ( path.node.name === specifier && path.parentPath.name === 'params' && ( isNgInject(path.parentPath.node) || isControllerOnObject(path.parentPath.parentPath) || isParameterOfAngularMethod(path.parentPath.parentPath) ) ); }) .closest(j.Program) .forEach((path) => { let importDeclaration = j.importDeclaration([j.importDefaultSpecifier(j.identifier(specifier))], j.literal(source)); if (isUseStrict(path.node.body[0])) { path.node.body.splice(1, 0, importDeclaration); } else { path.node.body.unshift(importDeclaration); } addedImport = true; }) .toSource({ arrowParensAlways: true, quote: 'single', }); if (addedImport) { // Remove injections using regex to retain code style output = output.replace(new RegExp(`\\h*${specifier}\\s*,\\s*`, 'g'), '') .replace(new RegExp(`\\s*,\\s*${specifier}\\h*(?=[,\\n\\)])`, 'g'), '') .replace(new RegExp(`\\(\\s*${specifier}\\s*\\)`, 'g'), '()'); } return output; };