///
'use strict';
import * as through from 'through2';
import * as falafel from 'falafel';
interface Pattern {
pattern?: RegExp;
type?: string;
firstLowerCase?: boolean;
removePattern?: boolean;
}
interface DecoratorPattern extends Pattern {
func?: string;
}
interface Options {
ignore?: RegExp;
moduleName?: string;
firstLowerCase?: boolean;
patterns?: Pattern[];
decoratorPatterns?: DecoratorPattern[];
decoratorModuleName?: string;
decorator?: boolean;
}
function setDefaultValue(opts: Options) {
if (typeof opts.ignore === 'undefined') {
opts.ignore = /^\$/;
}
if (typeof opts.moduleName === 'undefined') {
opts.moduleName = '';
}
if (typeof opts.firstLowerCase === 'undefined') {
opts.firstLowerCase = true;
}
if (typeof opts.patterns === 'undefined') {
opts.patterns = [
{ pattern: /Controller$/, type: 'controller', firstLowerCase: false },
{ pattern: /Ctrl$/, type: 'controller', firstLowerCase: false },
{ pattern: /Service$/, type: 'service' },
{ pattern: /Manager$/, type: 'service' },
{ pattern: /ServiceDelegate$/, type: 'service' },
{ pattern: /Provider$/, type: 'provider', removePattern: true },
{ pattern: /Directive$/, type: 'directive', removePattern: true, firstLowerCase: true }
];
}
if (typeof opts.decoratorPatterns === 'undefined') {
opts.decoratorPatterns = [
{ pattern: /Controller$/, func: 'Controller', firstLowerCase: false },
{ pattern: /Service$/, func: 'Service' },
{ pattern: /Provider$/, func: 'Provider', removePattern: true },
{ pattern: /Directive$/, func: 'Directive', removePattern: true, firstLowerCase: true }
];
}
}
module.exports = function angularify(opts: Options) {
opts = opts || {};
setDefaultValue(opts);
// creating a stream through which each file will pass
var stream = through.obj(function(file, enc, cb) {
if (file.isNull()) {
// do nothing
}
if (file.isBuffer()) {
var contents: string = file.contents.toString();
contents = transform(contents, opts);
file.contents = new Buffer(contents.toString());
}
this.push(file);
return cb();
});
// returning the file stream
return stream;
};
interface Node {
type: string;
declarations: Declaration[];
update(source:string);
source():string;
}
interface Declaration {
id: {
name: string
}
init: {
type: string,
callee: {
body: Body
}
}
}
interface Body {
body: DecoratorBlock[];
}
interface DecoratorBlock{
expression:{
right:any,
left:any
}
params:{
name:string
}[],
type:string
}
function transform(contents: string, opts: Options): string {
return falafel(contents, { tolerant: true }, function(node: Node) {
findClassDeclaration(node, opts);
}).toString();
}
function findClassDeclaration(node: Node, opts: Options) {
var decls: Declaration[], decl: Declaration;
if (node.type === 'VariableDeclaration' &&
(decls = node.declarations) && decls.length === 1 &&
(decl = decls[0]) && decl.init && decl.init.type === 'CallExpression') {
if (opts.ignore && decl.id.name.match(opts.ignore)) {
return;
}
if (!decl.init.callee.body) {
return;
}
if (opts.decorator) {
var decorators = findDecorator(decl);
decorators.forEach(function(decorator) {
opts.decoratorPatterns.forEach(function(decoratorPattern) {
if (decorator === opts.decoratorModuleName + '.' + decoratorPattern.func) {
addAngularModule(node, decl, opts, decoratorPattern);
}
});
});
}
else {
opts.patterns.forEach(function(pattern) {
if (decl.id.name.match(pattern.pattern)) {
addAngularModule(node, decl, opts, pattern);
}
});
}
}
}
function findDecorator(decl: Declaration) {
var body = decl.init.callee.body;
var decoratorBlock = body.body[body.body.length - 2];
var decorators = decoratorBlock.expression.right;
return decorators.arguments[0].elements.map(function(element) {
return element.source();
})
}
function addAngularModule(node:Node, decl:Declaration, opts:Options, ptn:Pattern) {
var moduleName = opts.moduleName,
type = ptn.type,
pattern = ptn.pattern,
removePattern = ptn.removePattern
var firstLowerCase = ptn.firstLowerCase;
if (typeof firstLowerCase === 'undefined') {
firstLowerCase = opts.firstLowerCase;
}
var constructor = decl.init.callee.body.body[0].type === 'FunctionDeclaration' ? decl.init.callee.body.body[0] : decl.init.callee.body.body[1];
var constructorParams = constructor.params.map(function(param) {
return '\'' + param.name + '\'';
});
var className = decl.id.name;
var conponentName = className;
if (removePattern) {
conponentName = decl.id.name.replace(pattern, '');
}
if (firstLowerCase) {
conponentName = conponentName.toLowerCase()[0] + conponentName.substring(1);
}
add$inject(decl.init.callee.body, className, conponentName, constructor, constructorParams);
function add$inject(body, className, componentName, constructor, constructorParams) {
if (!constructorParams) {
return;
}
var source = '/**/';
source += `${className}.$inject = [${constructorParams.join('\,')}]; `;
source += `${className}.$componentName = '${componentName}'`;
source += '/**/';
constructor.update(constructor.source() + source);
}
if (opts.decoratorModuleName) {
return;
}
var source = '/**/';
if (type === 'directive') {
source += createModule();
}
else if (type === 'value') {
source += createModule();
}
else if (type === 'constant') {
source += createModule();
}
else {
source += functionModule()
}
source += '/**/';
node.update(node.source() + source);
function functionModule() {
var source = '';
source += `angular.module('${moduleName}')`;
source += `.${type}('${conponentName}',${className});`;
return source;
}
function createModule() {
constructorParams.push(`function(){return new (Function.prototype.bind.apply(${className},[null].concat(Array.prototype.slice.call(arguments))));}`);
var source = '';
source += `angular.module('${moduleName}')`;
source += `.${type}('${conponentName}',[${constructorParams.join('\,')}]);`;
return source;
}
}