You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

175 lines
7.0 KiB

/**
* @fileoverview Rule to require sorting of import declarations
* @author Christian Schuller
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "enforce sorted import declarations within modules",
category: "ECMAScript 6",
recommended: false
},
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
}
]
},
create(context) {
const configuration = context.options[0] || {},
ignoreCase = configuration.ignoreCase || false,
ignoreMemberSort = configuration.ignoreMemberSort || false,
memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"];
let 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(node) {
if (previousDeclaration) {
const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
let currentLocalMemberName = getFirstLocalMemberName(node),
previousLocalMemberName = getFirstLocalMemberName(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,
message: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.",
data: {
syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex],
syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex]
}
});
}
} else {
if (previousLocalMemberName &&
currentLocalMemberName &&
currentLocalMemberName < previousLocalMemberName
) {
context.report({
node,
message: "Imports should be sorted alphabetically."
});
}
}
}
// Multiple members of an import declaration should also be sorted alphabetically.
if (!ignoreMemberSort && node.specifiers.length > 1) {
let previousSpecifier = null;
let previousSpecifierName = null;
for (let i = 0; i < node.specifiers.length; ++i) {
const currentSpecifier = node.specifiers[i];
if (currentSpecifier.type !== "ImportSpecifier") {
continue;
}
let currentSpecifierName = currentSpecifier.local.name;
if (ignoreCase) {
currentSpecifierName = currentSpecifierName.toLowerCase();
}
if (previousSpecifier && currentSpecifierName < previousSpecifierName) {
context.report({
node: currentSpecifier,
message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.",
data: {
memberName: currentSpecifier.local.name
}
});
}
previousSpecifier = currentSpecifier;
previousSpecifierName = currentSpecifierName;
}
}
previousDeclaration = node;
}
};
}
};