219 lines
25 KiB
JavaScript
219 lines
25 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
var _importType = require('../core/importType');
|
||
|
|
|
||
|
|
var _importType2 = _interopRequireDefault(_importType);
|
||
|
|
|
||
|
|
var _staticRequire = require('../core/staticRequire');
|
||
|
|
|
||
|
|
var _staticRequire2 = _interopRequireDefault(_staticRequire);
|
||
|
|
|
||
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||
|
|
|
||
|
|
const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index'];
|
||
|
|
|
||
|
|
// REPORTING
|
||
|
|
|
||
|
|
function reverse(array) {
|
||
|
|
return array.map(function (v) {
|
||
|
|
return {
|
||
|
|
name: v.name,
|
||
|
|
rank: -v.rank,
|
||
|
|
node: v.node
|
||
|
|
};
|
||
|
|
}).reverse();
|
||
|
|
}
|
||
|
|
|
||
|
|
function findOutOfOrder(imported) {
|
||
|
|
if (imported.length === 0) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
let maxSeenRankNode = imported[0];
|
||
|
|
return imported.filter(function (importedModule) {
|
||
|
|
const res = importedModule.rank < maxSeenRankNode.rank;
|
||
|
|
if (maxSeenRankNode.rank < importedModule.rank) {
|
||
|
|
maxSeenRankNode = importedModule;
|
||
|
|
}
|
||
|
|
return res;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function reportOutOfOrder(context, imported, outOfOrder, order) {
|
||
|
|
outOfOrder.forEach(function (imp) {
|
||
|
|
const found = imported.find(function hasHigherRank(importedItem) {
|
||
|
|
return importedItem.rank > imp.rank;
|
||
|
|
});
|
||
|
|
context.report(imp.node, '`' + imp.name + '` import should occur ' + order + ' import of `' + found.name + '`');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function makeOutOfOrderReport(context, imported) {
|
||
|
|
const outOfOrder = findOutOfOrder(imported);
|
||
|
|
if (!outOfOrder.length) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// There are things to report. Try to minimize the number of reported errors.
|
||
|
|
const reversedImported = reverse(imported);
|
||
|
|
const reversedOrder = findOutOfOrder(reversedImported);
|
||
|
|
if (reversedOrder.length < outOfOrder.length) {
|
||
|
|
reportOutOfOrder(context, reversedImported, reversedOrder, 'after');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
reportOutOfOrder(context, imported, outOfOrder, 'before');
|
||
|
|
}
|
||
|
|
|
||
|
|
// DETECTING
|
||
|
|
|
||
|
|
function computeRank(context, ranks, name, type) {
|
||
|
|
return ranks[(0, _importType2.default)(name, context)] + (type === 'import' ? 0 : 100);
|
||
|
|
}
|
||
|
|
|
||
|
|
function registerNode(context, node, name, type, ranks, imported) {
|
||
|
|
const rank = computeRank(context, ranks, name, type);
|
||
|
|
if (rank !== -1) {
|
||
|
|
imported.push({ name, rank, node });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function isInVariableDeclarator(node) {
|
||
|
|
return node && (node.type === 'VariableDeclarator' || isInVariableDeclarator(node.parent));
|
||
|
|
}
|
||
|
|
|
||
|
|
const types = ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'];
|
||
|
|
|
||
|
|
// Creates an object with type-rank pairs.
|
||
|
|
// Example: { index: 0, sibling: 1, parent: 1, external: 1, builtin: 2, internal: 2 }
|
||
|
|
// Will throw an error if it contains a type that does not exist, or has a duplicate
|
||
|
|
function convertGroupsToRanks(groups) {
|
||
|
|
const rankObject = groups.reduce(function (res, group, index) {
|
||
|
|
if (typeof group === 'string') {
|
||
|
|
group = [group];
|
||
|
|
}
|
||
|
|
group.forEach(function (groupItem) {
|
||
|
|
if (types.indexOf(groupItem) === -1) {
|
||
|
|
throw new Error('Incorrect configuration of the rule: Unknown type `' + JSON.stringify(groupItem) + '`');
|
||
|
|
}
|
||
|
|
if (res[groupItem] !== undefined) {
|
||
|
|
throw new Error('Incorrect configuration of the rule: `' + groupItem + '` is duplicated');
|
||
|
|
}
|
||
|
|
res[groupItem] = index;
|
||
|
|
});
|
||
|
|
return res;
|
||
|
|
}, {});
|
||
|
|
|
||
|
|
const omittedTypes = types.filter(function (type) {
|
||
|
|
return rankObject[type] === undefined;
|
||
|
|
});
|
||
|
|
|
||
|
|
return omittedTypes.reduce(function (res, type) {
|
||
|
|
res[type] = groups.length;
|
||
|
|
return res;
|
||
|
|
}, rankObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports) {
|
||
|
|
const getNumberOfEmptyLinesBetween = (currentImport, previousImport) => {
|
||
|
|
const linesBetweenImports = context.getSourceCode().lines.slice(previousImport.node.loc.end.line, currentImport.node.loc.start.line - 1);
|
||
|
|
|
||
|
|
return linesBetweenImports.filter(line => !line.trim().length).length;
|
||
|
|
};
|
||
|
|
let previousImport = imported[0];
|
||
|
|
|
||
|
|
imported.slice(1).forEach(function (currentImport) {
|
||
|
|
if (newlinesBetweenImports === 'always') {
|
||
|
|
if (currentImport.rank !== previousImport.rank && getNumberOfEmptyLinesBetween(currentImport, previousImport) === 0) {
|
||
|
|
context.report(previousImport.node, 'There should be at least one empty line between import groups');
|
||
|
|
} else if (currentImport.rank === previousImport.rank && getNumberOfEmptyLinesBetween(currentImport, previousImport) > 0) {
|
||
|
|
context.report(previousImport.node, 'There should be no empty line within import group');
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (getNumberOfEmptyLinesBetween(currentImport, previousImport) > 0) {
|
||
|
|
context.report(previousImport.node, 'There should be no empty line between import groups');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
previousImport = currentImport;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
meta: {
|
||
|
|
docs: {},
|
||
|
|
|
||
|
|
schema: [{
|
||
|
|
type: 'object',
|
||
|
|
properties: {
|
||
|
|
groups: {
|
||
|
|
type: 'array'
|
||
|
|
},
|
||
|
|
'newlines-between': {
|
||
|
|
enum: ['ignore', 'always', 'never']
|
||
|
|
}
|
||
|
|
},
|
||
|
|
additionalProperties: false
|
||
|
|
}]
|
||
|
|
},
|
||
|
|
|
||
|
|
create: function importOrderRule(context) {
|
||
|
|
const options = context.options[0] || {};
|
||
|
|
const newlinesBetweenImports = options['newlines-between'] || 'ignore';
|
||
|
|
let ranks;
|
||
|
|
|
||
|
|
try {
|
||
|
|
ranks = convertGroupsToRanks(options.groups || defaultGroups);
|
||
|
|
} catch (error) {
|
||
|
|
// Malformed configuration
|
||
|
|
return {
|
||
|
|
Program: function (node) {
|
||
|
|
context.report(node, error.message);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
let imported = [];
|
||
|
|
let level = 0;
|
||
|
|
|
||
|
|
function incrementLevel() {
|
||
|
|
level++;
|
||
|
|
}
|
||
|
|
function decrementLevel() {
|
||
|
|
level--;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
ImportDeclaration: function handleImports(node) {
|
||
|
|
if (node.specifiers.length) {
|
||
|
|
// Ignoring unassigned imports
|
||
|
|
const name = node.source.value;
|
||
|
|
registerNode(context, node, name, 'import', ranks, imported);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
CallExpression: function handleRequires(node) {
|
||
|
|
if (level !== 0 || !(0, _staticRequire2.default)(node) || !isInVariableDeclarator(node.parent)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const name = node.arguments[0].value;
|
||
|
|
registerNode(context, node, name, 'require', ranks, imported);
|
||
|
|
},
|
||
|
|
'Program:exit': function reportAndReset() {
|
||
|
|
makeOutOfOrderReport(context, imported);
|
||
|
|
|
||
|
|
if (newlinesBetweenImports !== 'ignore') {
|
||
|
|
makeNewlinesBetweenReport(context, imported, newlinesBetweenImports);
|
||
|
|
}
|
||
|
|
|
||
|
|
imported = [];
|
||
|
|
},
|
||
|
|
FunctionDeclaration: incrementLevel,
|
||
|
|
FunctionExpression: incrementLevel,
|
||
|
|
ArrowFunctionExpression: incrementLevel,
|
||
|
|
BlockStatement: incrementLevel,
|
||
|
|
ObjectExpression: incrementLevel,
|
||
|
|
'FunctionDeclaration:exit': decrementLevel,
|
||
|
|
'FunctionExpression:exit': decrementLevel,
|
||
|
|
'ArrowFunctionExpression:exit': decrementLevel,
|
||
|
|
'BlockStatement:exit': decrementLevel,
|
||
|
|
'ObjectExpression:exit': decrementLevel
|
||
|
|
};
|
||
|
|
}
|
||
|
|
};
|
||
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInJ1bGVzL29yZGVyLmpzIl0sIm5hbWVzIjpbImRlZmF1bHRHcm91cHMiLCJyZXZlcnNlIiwiYXJyYXkiLCJtYXAiLCJ2IiwibmFtZSIsInJhbmsiLCJub2RlIiwiZmluZE91dE9mT3JkZXIiLCJpbXBvcnRlZCIsImxlbmd0aCIsIm1heFNlZW5SYW5rTm9kZSIsImZpbHRlciIsImltcG9ydGVkTW9kdWxlIiwicmVzIiwicmVwb3J0T3V0T2ZPcmRlciIsImNvbnRleHQiLCJvdXRPZk9yZGVyIiwib3JkZXIiLCJmb3JFYWNoIiwiaW1wIiwiZm91bmQiLCJmaW5kIiwiaGFzSGlnaGVyUmFuayIsImltcG9ydGVkSXRlbSIsInJlcG9ydCIsIm1ha2VPdXRPZk9yZGVyUmVwb3J0IiwicmV2ZXJzZWRJbXBvcnRlZCIsInJldmVyc2VkT3JkZXIiLCJjb21wdXRlUmFuayIsInJhbmtzIiwidHlwZSIsInJlZ2lzdGVyTm9kZSIsInB1c2giLCJpc0luVmFyaWFibGVEZWNsYXJhdG9yIiwicGFyZW50IiwidHlwZXMiLCJjb252ZXJ0R3JvdXBzVG9SYW5rcyIsImdyb3VwcyIsInJhbmtPYmplY3QiLCJyZWR1Y2UiLCJncm91cCIsImluZGV4IiwiZ3JvdXBJdGVtIiwiaW5kZXhPZiIsIkVycm9yIiwiSlNPTiIsInN0cmluZ2lmeSIsInVuZGVmaW5lZCIsIm9taXR0ZWRUeXBlcyIsIm1ha2VOZXdsaW5lc0JldHdlZW5SZXBvcnQiLCJuZXdsaW5lc0JldHdlZW5JbXBvcnRzIiwiZ2V0TnVtYmVyT2ZFbXB0eUxpbmVzQmV0d2VlbiIsImN1cnJlbnRJbXBvcnQiLCJwcmV2aW91c0ltcG9ydCIsImxpbmVzQmV0d2VlbkltcG9ydHMiLCJnZXRTb3VyY2VDb2RlIiwibGluZXMiLCJzbGljZSIsImxvYyIsImVuZCIsImxpbmUiLCJzdGFydCIsInRyaW0iLCJtb2R1bGUiLCJleHBvcnRzIiwibWV0YSIsImRvY3MiLCJzY2hlbWEiLCJwcm9wZXJ0aWVzIiwiZW51bSIsImFkZGl0aW9uYWxQcm9wZXJ0aWVzIiwiY3JlYXRlIiwiaW1wb3J0T3JkZXJSdWxlIiwib3B0aW9ucyIsImVycm9yIiwiUHJvZ3JhbSIsIm1lc3NhZ2UiLCJsZXZlbCIsImluY3JlbWVudExldmVsIiwiZGVjcmVtZW50TGV2ZWwiLCJJbXBvcnREZWNsYXJhdGlvbiIsImhhbmRsZUltcG9ydHMiLCJzcGVjaWZpZXJzIiwic291cmNlIiwidmFsdWUiLCJDYWxsRXhwcmVzc2lvbiIsImhhbmRsZVJlcXVpcmVzIiwiYXJndW1lbnRzIiwicmVwb3J0QW5kUmVzZXQiLCJGdW5jdGlvbkRlY2xhcmF0aW9uIiwiRnVuY3Rpb25FeHByZXNzaW9uIiwiQXJyb3dGdW5jdGlvbkV4cHJlc3Npb24iLCJCbG9ja1N0YXRlbWVudCIsIk9iamVjdEV4cHJlc3Npb24iXSwibWFwcGluZ3MiOiJBQUFBOztBQUVBOzs7O0FBQ0E7Ozs7OztBQUVBLE1BQU1BLGdCQUFnQixDQUFDLFNBQUQsRUFBWSxVQUFaLEVBQXdCLFFBQXhCLEVBQWtDLFNBQWxDLEVBQTZDLE9BQTdDLENBQXRCOztBQUVBOztBQUVBLFNBQVNDLE9BQVQsQ0FBaUJDLEtBQWpCLEVBQXdCO0FBQ3RCLFNBQU9BLE1BQU1DLEdBQU4sQ0FBVSxVQUFVQyxDQUFWLEVBQWE7QUFDNUIsV0FBTztBQUNMQyxZQUFNRCxFQUFFQyxJQURIO0FBRUxDLFlBQU0sQ0FBQ0YsRUFBRUUsSUFGSjtBQUdMQyxZQUFNSCxFQUFFRztBQUhILEtBQVA7QUFLRCxHQU5NLEVBTUpOLE9BTkksRUFBUDtBQU9EOztBQUVELFNBQVNPLGNBQVQsQ0FBd0JDLFFBQXhCLEVBQWtDO0FBQ2hDLE1BQUlBLFNBQVNDLE1BQVQsS0FBb0IsQ0FBeEIsRUFBMkI7QUFDekIsV0FBTyxFQUFQO0FBQ0Q7QUFDRCxNQUFJQyxrQkFBa0JGLFNBQVMsQ0FBVCxDQUF0QjtBQUNBLFNBQU9BLFNBQVNHLE1BQVQsQ0FBZ0IsVUFBVUMsY0FBVixFQUEwQjtBQUMvQyxVQUFNQyxNQUFNRCxlQUFlUCxJQUFmLEdBQXNCSyxnQkFBZ0JMLElBQWxEO0FBQ0EsUUFBSUssZ0JBQWdCTCxJQUFoQixHQUF1Qk8sZUFBZVAsSUFBMUMsRUFBZ0Q7QUFDOUNLLHdCQUFrQkUsY0FBbEI7QUFDRDtBQUNELFdBQU9DLEdBQVA7QUFDRCxHQU5NLENBQVA7QUFPRDs7QUFFRCxTQUFTQyxnQkFBVCxDQUEwQkMsT0FBMUIsRUFBbUNQLFFBQW5DLEVBQTZDUSxVQUE3QyxFQUF5REMsS0FBekQsRUFBZ0U7QUFDOURELGFBQVdFLE9BQVgsQ0FBbUIsVUFBVUMsR0FBVixFQUFlO0FBQ2hDLFVBQU1DLFFBQVFaLFNBQVNhLElBQVQsQ0FBYyxTQUFTQyxhQUFULENBQXVCQyxZQUF2QixFQUFxQztBQUMvRCxhQUFPQSxhQUFhbEIsSUFBYixHQUFvQmMsSUFBSWQsSUFBL0I7QUFDRCxLQUZhLENBQWQ7QUFHQVUsWUFBUVMsTUFBUixDQUFlTCxJQUFJYixJQUFuQixFQUF5QixNQUFNYSxJQUFJZixJQUFWLEdBQWlCLHdCQUFqQixHQUE0Q2EsS0FBNUMsR0FDdkIsY0FEdUIsR0FDTkcsTUFBTWhCLElBREEsR0FDTyxHQURoQztBQUVELEdBTkQ7QUFPRDs7QUFFRCxTQUFTcUIsb0JBQVQsQ0FBOEJWLE9BQTlCLEVBQXVDUCxRQUF2QyxFQUFpRDtBQUMvQyxRQUFNUSxhQUFhVCxlQUFlQyxRQUFmLENBQW5CO0FBQ0EsTUFBSSxDQUFDUSxXQUFXUCxNQUFoQixFQUF3QjtBQUN0QjtBQUNEO0FBQ0Q7QUFDQSxRQUFNaUIsbUJBQW1CMUIsUUFBUVEsUUFBUixDQUF6QjtBQUNBLFFBQU1tQixnQkFBZ0JwQixlQUFlbUIsZ0JBQWYsQ0FBdEI7QUFDQSxNQUFJQyxjQUFjbEIsTUFBZCxHQUF1Qk8sV0FBV1AsTUFBdEMsRUFBOEM7QUFDNUNLLHFCQUFpQkMsT0FBakIsRUFBMEJXLGdCQUExQixFQUE0Q0MsYUFBNUMsRUFBMkQsT0FBM0Q7QUFDQTtBQUNEO0FBQ0RiLG1CQUFpQkMsT0FBakIsRUFBMEJQLFFBQTFCLEVBQW9DUSxVQUFwQyxFQUFnRCxRQUFoRDtBQUNEOztBQUVEOztBQUVBLFNBQVNZLFdBQVQsQ0FBcUJiLE9BQXJCLEVBQThCYyxLQUE5QixFQUFxQ3pCLElBQXJDLEVBQTJDMEIsSUFBM0MsRUFBaUQ7QUFDL0MsU0FBT0QsTUFBTSwwQkFBV3pCLElBQVgsRUFBaUJXLE9BQWpCLENBQU4sS0FDSmUsU0FBUyxRQUFULEdBQW9CLENBQXBCLEdBQXdCLEdBRHBCLENBQVA7QUFFRDs7QUFFRCxTQUFTQyxZQUFULENBQXNCaEIsT0FBdEIsRUFBK0JULElBQS9CLEVBQXFDRixJQUFyQyxFQUEyQzBCLElBQTNDLEVBQWlERCxLQUFqRCxFQUF3RHJCLFFBQXhELEVBQWtFO0FBQ2hFLFFBQU1ILE9BQU91QixZQUFZYixPQUFaLEVBQXFCYyxLQUFyQixFQUE0QnpCLElBQTVCLEVBQ
|