@roadmunk/eslint-plugin-roadmunk-custom
Advanced tools
Comparing version 1.6.1 to 1.7.0
@@ -41,2 +41,17 @@ 'use strict'; | ||
/** | ||
* Checks if the given node is a require statement function call | ||
* | ||
* @param {ASTNode} node - the node whose properties need to be looked up | ||
* @returns {Boolean} true if it is a require function call with a single argument literal. Otherwise, returns false | ||
*/ | ||
function isRequireCall(node) { | ||
return node | ||
&& node.type === 'CallExpression' | ||
&& node.callee.type === 'Identifier' | ||
&& node.callee.name === 'require' | ||
&& node.arguments.length === 1 | ||
&& node.arguments[0].type === 'Literal'; | ||
} | ||
/** | ||
* node : The node on which lint rule has fired. (MemberExpression) | ||
@@ -99,22 +114,8 @@ * context : context object | ||
/** | ||
* Checks if the given node is a require statement function call | ||
* | ||
* @param {Object} node - the node whose properties need to be looked up | ||
* @returns {Boolean} true if it is a require function call with a single argument literal. Otherwise, returns false | ||
*/ | ||
function isRequire(node) { | ||
return node | ||
&& node.callee.type === 'Identifier' | ||
&& node.callee.name === 'require' | ||
&& node.arguments.length === 1 | ||
&& node.arguments[0].type === 'Literal'; | ||
} | ||
module.exports = { | ||
lodashAutofix, | ||
hasPropsWithValues, | ||
isRequire, | ||
isPropertyAnIdentifierWithName, | ||
isObjectAnIdentifierWithName, | ||
isRequireCall, | ||
}; |
@@ -7,4 +7,4 @@ /** | ||
const _ = require('lodash'); | ||
const { isRequire } = require('../helper.js'); | ||
const _ = require('lodash'); | ||
const { isRequireCall } = require('../helper.js'); | ||
@@ -27,3 +27,3 @@ // ------------------------------------------------------------------------------ | ||
CallExpression(node) { | ||
if (!isRequire(node)) { | ||
if (!isRequireCall(node)) { | ||
return; | ||
@@ -30,0 +30,0 @@ } |
@@ -7,4 +7,5 @@ /** | ||
const _ = require('lodash'); | ||
const { isRequire } = require('../helper.js'); | ||
const _ = require('lodash'); | ||
const { generateReport, registerNode } = require('../order-common'); | ||
const { isRequireCall } = require('../helper'); | ||
@@ -14,68 +15,3 @@ // ------------------------------------------------------------------------------ | ||
// ------------------------------------------------------------------------------ | ||
const defaultOrder = [ '@roadmunk/', 'lib/', 'common/lib/', 'models/', 'common/models/', 'views/', 'common/views/', 'tests/', 'common/tests', 'text!' ]; | ||
function isRelativeRequire(moduleName) { | ||
return moduleName.startsWith('.'); | ||
} | ||
function isSubModule(moduleName) { | ||
return moduleName.includes('/'); | ||
} | ||
function computeRank(name) { | ||
// Find which item in order matches the module name | ||
const match = defaultOrder.filter(order => name.includes(order)); | ||
let rank = 0; // If no match is found, we will assume that it's a top level require | ||
if (match.length === 0) { | ||
// We don't know about this so just push it to the bottom | ||
// Multiply by two because each match can have two different ranks (one for relative requires) | ||
if (isSubModule(name)) { | ||
rank = (defaultOrder.length + 1) * 2; | ||
} | ||
// Relative requires belong at the bottom, even under the unknown requires | ||
else if (isRelativeRequire(name)) { | ||
rank = ((defaultOrder.length + 1) * 2) + 2; | ||
} | ||
} | ||
else { | ||
// If module name matches multiple things then take the highest rank | ||
rank = defaultOrder.indexOf(match[match.length - 1]) + 1; | ||
// Multi by two to accomodate for relative requires they will be slotted after this (rank + 1) | ||
rank *= 2; | ||
// Place relative requires at the end | ||
if (isRelativeRequire(name)) { | ||
rank++; | ||
} | ||
} | ||
return rank; | ||
} | ||
function registerNode(node, name, imported) { | ||
const rank = computeRank(name); | ||
imported.push({ name, rank, node }); | ||
} | ||
function detectOutOfOrder(imported) { | ||
let maxRank = 0; | ||
// "imported" is an array corresponding to require statements. Each element in the array has a rank which hints to us about where it needs to be | ||
// Eg: If @roadmunk/test module has rank 1 & models/test has rank 2 & @roadmunk/test comes after models then it'll be added to outOfOrderNodes | ||
const outOfOrderNodes = imported.filter(function(importedModule) { | ||
// We found something that has been required lower than where it should have been | ||
if (importedModule.rank < maxRank) { | ||
return true; | ||
} | ||
maxRank = importedModule.rank; | ||
return false; | ||
}); | ||
return outOfOrderNodes; | ||
} | ||
function findRootNode(node) { | ||
@@ -101,3 +37,3 @@ let parent = node; | ||
function isTopLevelRequire(node) { | ||
function isTopLevelDeclaration(node) { | ||
const declaration = findVariableDeclaration(node); | ||
@@ -116,64 +52,9 @@ return _.get(declaration, 'parent.type') === 'Program'; | ||
}, | ||
create : function(context) { | ||
const imported = []; | ||
const list = []; | ||
function report(nodeInfo, higherRankedNodeInfo) { | ||
context.report({ | ||
node : nodeInfo.node, | ||
message : `${nodeInfo.name} was not required in the correct order`, | ||
fix : function(fixer) { | ||
const sourceCode = context.getSourceCode(); | ||
// firstRoot refers to the node that is out of order. We will move this to another location | ||
const firstRoot = findRootNode(nodeInfo.node); | ||
const firstRootStart = firstRoot.start; | ||
let firstRootEnd = firstRoot.end; | ||
const comments = context.getComments(firstRoot); | ||
// If a trailing there is a trailing comment then change the firstRootEnd to that | ||
if (comments.trailing.length > 0) { | ||
comments.trailing.forEach(comment => { | ||
if (comment.loc.start.line === firstRoot.loc.end.line) { | ||
firstRootEnd = Math.max(firstRootEnd, comment.end); | ||
} | ||
}); | ||
} | ||
const oldCode = `${sourceCode.text.substring(firstRootStart, firstRootEnd)}\n`; | ||
// secondRoot refers to the node above which the firstRoot belongs | ||
const secondRoot = findRootNode(higherRankedNodeInfo.node); | ||
const secondRootStart = secondRoot.start; | ||
const secondRootEnd = secondRoot.end; | ||
const newCode = `${sourceCode.text.substring(secondRootStart, secondRootEnd)}`; | ||
// Remove the first root since it'll be moved to another place anyway | ||
// Removing until firstRootEnd + 1 to avoid leaving behind a blank line | ||
const removeNode = fixer.removeRange([ firstRootStart, firstRootEnd + 1 ]); | ||
// Replace the second root's range with autofixed code | ||
const fix = fixer.replaceTextRange([ secondRootStart, secondRootEnd ], oldCode + newCode); | ||
// Since we're doing two things in this autofix return both of them | ||
return [ fix, removeNode ]; | ||
}, | ||
}); | ||
} | ||
function generateReport(imported) { | ||
const outOfOrderNodes = detectOutOfOrder(imported); | ||
if (!outOfOrderNodes.length) { | ||
return; | ||
} | ||
// For each out of order require, find the one before which it belongs | ||
outOfOrderNodes.forEach(outOfOrder => { | ||
const found = imported.find(importedModule => importedModule.rank > outOfOrder.rank); | ||
report(outOfOrder, found); | ||
}); | ||
} | ||
return { | ||
CallExpression(node) { | ||
if (!isRequire(node) || !isTopLevelRequire(node)) { | ||
if (!isRequireCall(node) || !isTopLevelDeclaration(node)) { | ||
return; | ||
@@ -183,6 +64,8 @@ } | ||
const moduleName = node.arguments[0].value; | ||
registerNode(node, moduleName, imported); | ||
const rootNode = findRootNode(node); | ||
registerNode(node, rootNode, moduleName, list); | ||
}, | ||
'Program:exit' : function() { | ||
generateReport(imported); | ||
generateReport(context, list, nodeInfo => `${nodeInfo.moduleName} was not required in the correct order`); | ||
}, | ||
@@ -189,0 +72,0 @@ }; |
{ | ||
"name": "@roadmunk/eslint-plugin-roadmunk-custom", | ||
"version": "1.6.1", | ||
"version": "1.7.0", | ||
"description": "Plugin to hold custom ESLint rules for Roadmunk", | ||
@@ -24,3 +24,3 @@ "keywords": [ | ||
"devDependencies": { | ||
"@roadmunk/eslint-config-roadmunk": "^3.6.1", | ||
"@roadmunk/eslint-config-roadmunk": "^3.8.1", | ||
"eslint": "^5.1.0", | ||
@@ -27,0 +27,0 @@ "mocha": "^5.2.0" |
@@ -46,16 +46,20 @@ # eslint-plugin-roadmunk-custom | ||
`no-lodash-isundefined` : Prevents usage of `_.isUndefined` method | ||
`align-assign` : Ensure that assignment statements are aligned | ||
`assert-length` : Uses the correct assertion method to check for length of an array | ||
`log-message-length` : Enforces a specified max length on log messages | ||
`no-lodash-deprecated-functions` : Prevents usage of deprecated lodash functions. | ||
`no-lodash-isnull` : Prevents usage of `_.isNull` method. | ||
`no-align-assign` : Ensure that assignment statements are aligned | ||
`no-lodash-isundefined` : Prevents usage of `_.isUndefined` method | ||
`order-require` : Ensures that require statements in a file are in the correct order. | ||
`no-log-info` : Prevents the use of `log.info`. Our coding standards recommend usage of `log.user` & `log.sys` over the deprecated `log.info` | ||
`assert-length` : Uses the correct assertion method to check for length of an array | ||
`no-require-views` : Warns about requiring packages from the `views/` folder. | ||
`log-message-length` : Enforces a specified max length on log messages | ||
`order-import` : Ensures that import statements in a file are in the correct order. | ||
`no-lodash-deprecated-functions` : Prevents usage of deprecated lodash functions. | ||
`no-require-views` : Warns about requiring packages from the `views/` folder. | ||
`order-require` : Ensures that require statements in a file are in the correct order. |
@@ -21,8 +21,8 @@ /** | ||
valid : [ | ||
'log.debug("Valid messsage")', | ||
'log.info("Valid messsage")', | ||
'log.warn("Valid messsage")', | ||
'log.error("Valid messsage")', | ||
'log.command("Valid messsage")', | ||
'log.command("Valid messsage", { testing : true })', | ||
'log.debug("Valid message")', | ||
'log.info("Valid message")', | ||
'log.warn("Valid message")', | ||
'log.error("Valid message")', | ||
'log.command("Valid message")', | ||
'log.command("Valid message", { testing : true })', | ||
'log.random("This is a string that is long enough that it goes over the limit")', | ||
@@ -29,0 +29,0 @@ 'console.log("This is a string that is long enough that it goes over the limit")', |
86551
37
2481
65