/** * @fileoverview Rule to require sorting of import declarations * @author Christian Schuller * @copyright 2015 Christian Schuller. All rights reserved. * See LICENSE file in root directory for full license. */ "use strict"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = function(context) { var configuration = context.options[0] || {}, ignoreCase = configuration.ignoreCase || false, ignoreMemberSort = configuration.ignoreMemberSort || false, memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"], previousDeclaration = null; /** * Gets the used member syntax style. * * import "my-module.js" --> none * import * as myModule from "my-module.js" --> all * import {myMember} from "my-module.js" --> single * import {foo, bar} from "my-module.js" --> multiple * * @param {ASTNode} node - the ImportDeclaration node. * @returns {string} used member parameter style, ["all", "multiple", "single"] */ function usedMemberSyntax(node) { if (node.specifiers.length === 0) { return "none"; } else if (node.specifiers[0].type === "ImportNamespaceSpecifier") { return "all"; } else if (node.specifiers.length === 1) { return "single"; } else { return "multiple"; } } /** * Gets the group by member parameter index for given declaration. * @param {ASTNode} node - the ImportDeclaration node. * @returns {number} the declaration group by member index. */ function getMemberParameterGroupIndex(node) { return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node)); } /** * Gets the local name of the first imported module. * @param {ASTNode} node - the ImportDeclaration node. * @returns {?string} the local name of the first imported module. */ function getFirstLocalMemberName(node) { if (node.specifiers[0]) { return node.specifiers[0].local.name; } else { return null; } } return { "ImportDeclaration": function(node) { if (previousDeclaration) { var currentLocalMemberName = getFirstLocalMemberName(node), currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node), previousLocalMemberName = getFirstLocalMemberName(previousDeclaration), previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration); if (ignoreCase) { previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase(); currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase(); } // When the current declaration uses a different member syntax, // then check if the ordering is correct. // Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name. if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) { if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) { context.report({ node: node, message: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.", data: { syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex], syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex] } }); } } else { if (previousLocalMemberName && currentLocalMemberName && currentLocalMemberName < previousLocalMemberName ) { context.report({ node: node, message: "Imports should be sorted alphabetically." }); } } } // Multiple members of an import declaration should also be sorted alphabetically. if (!ignoreMemberSort && node.specifiers.length > 1) { node.specifiers.reduce(function(previousSpecifier, currentSpecifier) { var currentSpecifierName = currentSpecifier.local.name, previousSpecifierName = previousSpecifier.local.name; if (ignoreCase) { currentSpecifierName = currentSpecifierName.toLowerCase(); previousSpecifierName = previousSpecifierName.toLowerCase(); } if (currentSpecifierName < previousSpecifierName) { context.report({ node: currentSpecifier, message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.", data: { memberName: currentSpecifier.local.name } }); } return currentSpecifier; }, node.specifiers[0]); } previousDeclaration = node; } }; }; module.exports.schema = [ { "type": "object", "properties": { "ignoreCase": { "type": "boolean" }, "memberSyntaxSortOrder": { "type": "array", "items": { "enum": ["none", "all", "multiple", "single"] }, "uniqueItems": true, "minItems": 4, "maxItems": 4 }, "ignoreMemberSort": { "type": "boolean" } }, "additionalProperties": false } ];