Skip to content

Commit

Permalink
feat(eslint): Create no-ng-module-export rule that prefers exporting …
Browse files Browse the repository at this point in the history
…an NG module name, not the module object (with fixer)
  • Loading branch information
christopherthielen committed Dec 13, 2019
1 parent 923f5ae commit 39cfaaa
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/eslint-plugin/base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
extends: ['eslint:recommended', 'prettier', 'prettier/@typescript-eslint', 'plugin:@typescript-eslint/recommended'],
rules: {
'@spinnaker/strictdi': 2,
'@spinnaker/no-ng-module-export': 2,
indent: 'off',
'member-ordering': 'off',
'no-console': ['error', { allow: ['warn', 'error'] }],
Expand Down
6 changes: 5 additions & 1 deletion packages/eslint-plugin/eslint-plugin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module.exports = {
rules: {},
rules: {
'component-literal': require('./rules/component-literal'),
strictdi: require('./rules/strictdi'),
'no-ng-module-export': require('./rules/no-ng-module-export'),
},
configs: {
base: require('./base.config.js'),
},
Expand Down
110 changes: 110 additions & 0 deletions packages/eslint-plugin/rules/no-ng-module-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use strict';

/**
* Prefer exporting a module's NAME instead of the entire angular.module()
*
* @version 0.1.0
* @category conventions
* @sinceAngularVersion 1.x
*/
const rule = function(context) {
function getSuggestedVariableNameForFile() {
const filename = context.getFilename();
if (filename.includes('/modules/')) {
return filename
.replace(/^.*\/modules\//g, '')
.replace(/\/src\//g, '/')
.replace(/\.[\w]*$/g, '')
.replace(/[^\w_]/g, '_')
.toUpperCase();
}
}

return {
AssignmentExpression: function(node) {
const { left = {}, right = {} } = node;
const isModuleExports = isModuleExportMemberExpression(left);
const moduleNameNode = getAngularModuleNameNode(right);
if (isModuleExports && moduleNameNode) {
const message = 'Prefer exporting the AngularJS module name instead of the entire module';
const variableName = getSuggestedVariableNameForFile();

function fix(fixer) {
const assignmentRange = [left.range[0], right.range[0]];
const exportModuleVariable = `export const ${variableName} = ${moduleNameNode.raw};\n`;
const exportNameVariable = `export const name = ${variableName}; // for backwards compatibility\n`;
return [
// Insert 'export const FOO = 'foo';
fixer.insertTextBefore(node, exportModuleVariable + exportNameVariable),
// Remove 'module.exports = '
fixer.replaceTextRange(assignmentRange, ''),
// Replace 'angular.module("foo"' with 'angular.module(FOO'
fixer.replaceText(moduleNameNode, variableName),
];
}

if (variableName) {
context.report({ node, message, fix });
} else {
context.report({ node, message });
}
}
},
};
};

function isModuleExportMemberExpression(node) {
const { object = {}, property = {} } = node;
const isModuleExports = node.type === 'MemberExpression' && object.name === 'module' && property.name === 'exports';
const isBareExports = node.type === 'Identifier' && node.name === 'exports';
return isModuleExports || isBareExports;
}

function getAngularModuleNameNode(node) {
const { callee } = node;
if (node.type !== 'CallExpression') return false;

function angularModuleNameNode(callExpression) {
const isLiteral =
callExpression.arguments && callExpression.arguments[0] && callExpression.arguments[0].type === 'Literal';
return isLiteral ? callExpression.arguments[0] : undefined;
}

function isChainedCallExpression(_callee) {
return _callee.type === 'MemberExpression' && callee.object && callee.object.type === 'CallExpression';
}

function isAngularModuleCall(_callee) {
return (
_callee.type === 'MemberExpression' &&
callee.object &&
callee.object.type === 'Identifier' &&
callee.object.name === 'angular' &&
callee.property &&
callee.property.name === 'module'
);
}

function isRawModuleCall(_callee) {
return _callee.type === 'Identifier' && callee.name === 'module';
}

if (isChainedCallExpression(callee)) {
return getAngularModuleNameNode(callee.object, node);
} else if (isRawModuleCall(callee)) {
if (node.arguments && node.arguments[0] && node.arguments[0].type === 'Literal') return angularModuleNameNode(node);
} else if (isAngularModuleCall(callee)) {
return angularModuleNameNode(node);
}
}

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Instead of exporting the angular.module(), export the modules string identifier',
},
fixable: 'code',
},
create: rule,
};

0 comments on commit 39cfaaa

Please sign in to comment.