eslint-plugin-sort-class-members
Advanced tools
Comparing version 0.5.0 to 1.0.0
@@ -10,7 +10,12 @@ 'use strict'; | ||
}, | ||
rulesConfig: { | ||
'sort-class-members': [0, { | ||
order: ['[static-properties]', '[static-methods]', '[properties]', '[conventional-private-properties]', 'constructor', '[methods]', '[conventional-private-methods]'] | ||
}] | ||
configs: { | ||
recommended: { | ||
rules: { | ||
'sort-class-members/sort-class-members': [2, { | ||
order: ['[static-properties]', '[static-methods]', '[properties]', '[conventional-private-properties]', 'constructor', '[methods]', '[conventional-private-methods]'], | ||
accessorPairPositioning: 'getThenSet' | ||
}] | ||
} | ||
} | ||
} | ||
}; |
@@ -18,2 +18,5 @@ 'use strict'; | ||
type: 'boolean' | ||
}, | ||
accessorPairPositioning: { | ||
enum: ['getThenSet', 'setThenGet', 'together', 'any'] | ||
} | ||
@@ -30,3 +33,4 @@ }, | ||
type: { enum: ['method', 'property'] }, | ||
static: { type: 'boolean' } | ||
static: { type: 'boolean' }, | ||
kind: { enum: ['get', 'set'] } | ||
}, | ||
@@ -33,0 +37,0 @@ additionalProperties: false |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.sortClassMembers = undefined; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
@@ -9,7 +14,2 @@ | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.sortClassMembers = undefined; | ||
var _schema = require('./schema'); | ||
@@ -24,26 +24,16 @@ | ||
function sortClassMembersRule(context) { | ||
var sourceCode = context.getSourceCode(); | ||
var options = context.options[0] || {}; | ||
var stopAfterFirst = !!options.stopAfterFirstProblem; | ||
var accessorPairPositioning = options.accessorPairPositioning || 'getThenSet'; | ||
var order = options.order || defaults.order || []; | ||
var groups = _extends({}, builtInGroups, defaults.groups, options.groups); | ||
var orderedSlots = getExpectedOrder(order, groups); | ||
var groupAccessors = accessorPairPositioning !== 'any'; | ||
return { | ||
'ClassDeclaration': function ClassDeclaration(node) { | ||
var classMemberNodes = node.body.body; | ||
var members = getClassMemberInfos(node, context.getSourceCode(), orderedSlots); | ||
var members = classMemberNodes.map(function (member) { | ||
var memberInfo = getMemberInfo(member, sourceCode); | ||
var acceptableSlots = getAcceptableSlots(memberInfo, orderedSlots); | ||
return _extends({}, memberInfo, { acceptableSlots: acceptableSlots }); | ||
}) | ||
// ignore members that don't match any slots | ||
.filter(function (member) { | ||
return member.acceptableSlots.length; | ||
}); | ||
var problems = findProblems(members, orderedSlots); | ||
var problemCount = problems.length; | ||
// check for out-of-order and separated get/set pairs | ||
var accessorPairProblems = findAccessorPairProblems(members, accessorPairPositioning); | ||
var _iteratorNormalCompletion = true; | ||
@@ -54,24 +44,8 @@ var _didIteratorError = false; | ||
try { | ||
for (var _iterator = problems[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _step$value = _step.value; | ||
var source = _step$value.source; | ||
var target = _step$value.target; | ||
var expected = _step$value.expected; | ||
for (var _iterator = accessorPairProblems[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var problem = _step.value; | ||
var reportData = { | ||
source: getMemberDescription(source), | ||
target: getMemberDescription(target), | ||
expected: expected | ||
}; | ||
var message = 'Expected {{ source }} to come immediately {{ expected }} {{ target }}.'; | ||
var message = 'Expected {{ source }} to come {{ expected }} {{ target }}.'; | ||
if (stopAfterFirst && problemCount > 1) { | ||
message += ' ({{ more }} similar {{ problem }} in this class)'; | ||
reportData.more = problemCount - 1; | ||
reportData.problem = problemCount === 2 ? 'problem' : 'problems'; | ||
} | ||
context.report({ node: source.node, message: message, data: reportData }); | ||
reportProblem({ problem: problem, context: context, message: message, stopAfterFirst: stopAfterFirst, problemCount: problemCount }); | ||
if (stopAfterFirst) { | ||
@@ -81,2 +55,5 @@ break; | ||
} | ||
// filter out the second accessor in each pair so we only detect one problem | ||
// for out-of-order accessor pairs | ||
} catch (err) { | ||
@@ -96,2 +73,44 @@ _didIteratorError = true; | ||
} | ||
members = members.filter(function (m) { | ||
return !(m.matchingAccessor && !m.isFirstAccessor); | ||
}); | ||
// ignore members that don't match any slots | ||
members = members.filter(function (member) { | ||
return member.acceptableSlots.length; | ||
}); | ||
// check member positions against rule order | ||
var problems = findProblems(members, orderedSlots); | ||
var problemCount = problems.length; | ||
var _iteratorNormalCompletion2 = true; | ||
var _didIteratorError2 = false; | ||
var _iteratorError2 = undefined; | ||
try { | ||
for (var _iterator2 = problems[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { | ||
var problem = _step2.value; | ||
var message = 'Expected {{ source }} to come {{ expected }} {{ target }}.'; | ||
reportProblem({ problem: problem, message: message, context: context, stopAfterFirst: stopAfterFirst, problemCount: problemCount, groupAccessors: groupAccessors }); | ||
if (stopAfterFirst) { | ||
break; | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError2 = true; | ||
_iteratorError2 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion2 && _iterator2.return) { | ||
_iterator2.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError2) { | ||
throw _iteratorError2; | ||
} | ||
} | ||
} | ||
} | ||
@@ -107,10 +126,61 @@ }; | ||
function getMemberDescription(memberInfo) { | ||
if (memberInfo.node.kind === 'constructor') { | ||
function reportProblem(_ref) { | ||
var problem = _ref.problem; | ||
var message = _ref.message; | ||
var context = _ref.context; | ||
var stopAfterFirst = _ref.stopAfterFirst; | ||
var problemCount = _ref.problemCount; | ||
var groupAccessors = _ref.groupAccessors; | ||
var source = problem.source; | ||
var target = problem.target; | ||
var expected = problem.expected; | ||
var reportData = { | ||
source: getMemberDescription(source, { groupAccessors: groupAccessors }), | ||
target: getMemberDescription(target, { groupAccessors: groupAccessors }), | ||
expected: expected | ||
}; | ||
if (stopAfterFirst && problemCount > 1) { | ||
message += ' ({{ more }} similar {{ problem }} in this class)'; | ||
reportData.more = problemCount - 1; | ||
reportData.problem = problemCount === 2 ? 'problem' : 'problems'; | ||
} | ||
context.report({ node: source.node, message: message, data: reportData }); | ||
} | ||
function getMemberDescription(member, _ref2) { | ||
var groupAccessors = _ref2.groupAccessors; | ||
if (member.kind === 'constructor') { | ||
return 'constructor'; | ||
} | ||
return '' + (memberInfo.static ? 'static ' : '') + memberInfo.type + ' ' + memberInfo.name; | ||
var typeName = undefined; | ||
if (member.matchingAccessor && groupAccessors) { | ||
typeName = 'accessor pair'; | ||
} else if (isAccessor(member)) { | ||
typeName = member.kind + 'ter'; | ||
} else { | ||
typeName = member.type; | ||
} | ||
return '' + (member.static ? 'static ' : '') + typeName + ' ' + member.name; | ||
} | ||
function getClassMemberInfos(classDeclaration, sourceCode, orderedSlots) { | ||
var classMemberNodes = classDeclaration.body.body; | ||
var members = classMemberNodes.map(function (member, i) { | ||
return _extends({}, getMemberInfo(member, sourceCode), { id: String(i) }); | ||
}).map(function (memberInfo, i, memberInfos) { | ||
matchAccessorPairs(memberInfos); | ||
var acceptableSlots = getAcceptableSlots(memberInfo, orderedSlots); | ||
return _extends({}, memberInfo, { acceptableSlots: acceptableSlots }); | ||
}); | ||
return members; | ||
} | ||
function getMemberInfo(node, sourceCode) { | ||
@@ -136,14 +206,21 @@ var name = undefined; | ||
return { name: name, type: type, static: node.static, node: node }; | ||
return { name: name, type: type, static: node.static, kind: node.kind, node: node }; | ||
} | ||
function findProblems(members) { | ||
function findAccessorPairProblems(members, positioning) { | ||
var problems = []; | ||
if (positioning === 'any') { | ||
return problems; | ||
} | ||
members.forEach(function (first, firstIndex) { | ||
members.slice(firstIndex + 1).forEach(function (second) { | ||
if (!areMembersInCorrectOrder(first, second)) { | ||
problems.push({ source: second, target: first, expected: 'before' }); | ||
forEachPair(members, function (first, second, firstIndex, secondIndex) { | ||
if (first.matchingAccessor === second.id) { | ||
var outOfOrder = positioning === 'getThenSet' && first.kind !== 'get' || positioning === 'setThenGet' && first.kind !== 'set'; | ||
var outOfPosition = secondIndex - firstIndex !== 1; | ||
if (outOfOrder || outOfPosition) { | ||
var expected = outOfOrder ? 'before' : 'after'; | ||
problems.push({ source: second, target: first, expected: expected }); | ||
} | ||
}); | ||
} | ||
}); | ||
@@ -154,2 +231,22 @@ | ||
function findProblems(members) { | ||
var problems = []; | ||
forEachPair(members, function (first, second) { | ||
if (!areMembersInCorrectOrder(first, second)) { | ||
problems.push({ source: second, target: first, expected: 'before' }); | ||
} | ||
}); | ||
return problems; | ||
} | ||
function forEachPair(list, callback) { | ||
list.forEach(function (first, firstIndex) { | ||
list.slice(firstIndex + 1).forEach(function (second, secondIndex) { | ||
callback(first, second, firstIndex, firstIndex + secondIndex + 1); | ||
}); | ||
}); | ||
} | ||
function areMembersInCorrectOrder(first, second) { | ||
@@ -167,4 +264,4 @@ return first.acceptableSlots.some(function (a) { | ||
}) // check member against each slot | ||
.filter(function (_ref) { | ||
var score = _ref.score; | ||
.filter(function (_ref3) { | ||
var score = _ref3.score; | ||
return score > 0; | ||
@@ -175,8 +272,8 @@ }) // discard slots that don't match | ||
}) // sort best matching slots first | ||
.filter(function (_ref2, i, array) { | ||
var score = _ref2.score; | ||
.filter(function (_ref4, i, array) { | ||
var score = _ref4.score; | ||
return score === array[0].score; | ||
}) // take top scoring slots | ||
.map(function (_ref3) { | ||
var index = _ref3.index; | ||
.map(function (_ref5) { | ||
var index = _ref5.index; | ||
return index; | ||
@@ -192,6 +289,6 @@ }) // we only need an array of slot indexes | ||
var scores = comparers.map(function (_ref4) { | ||
var property = _ref4.property; | ||
var value = _ref4.value; | ||
var test = _ref4.test; | ||
var scores = comparers.map(function (_ref6) { | ||
var property = _ref6.property; | ||
var value = _ref6.value; | ||
var test = _ref6.test; | ||
@@ -252,2 +349,19 @@ if (slot[property] !== undefined) { | ||
function isAccessor(_ref7) { | ||
var kind = _ref7.kind; | ||
return kind === 'get' || kind === 'set'; | ||
} | ||
function matchAccessorPairs(members) { | ||
forEachPair(members, function (first, second) { | ||
var isMatch = first.name === second.name && first.static === second.static; | ||
if (isAccessor(first) && isAccessor(second) && isMatch) { | ||
first.isFirstAccessor = true; | ||
first.matchingAccessor = second.id; | ||
second.matchingAccessor = first.id; | ||
} | ||
}); | ||
} | ||
function getNameComparer(name) { | ||
@@ -286,9 +400,9 @@ if (name[0] === '/') { | ||
var _iteratorNormalCompletion2 = true; | ||
var _didIteratorError2 = false; | ||
var _iteratorError2 = undefined; | ||
var _iteratorNormalCompletion3 = true; | ||
var _didIteratorError3 = false; | ||
var _iteratorError3 = undefined; | ||
try { | ||
for (var _iterator2 = collection[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { | ||
var item = _step2.value; | ||
for (var _iterator3 = collection[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { | ||
var item = _step3.value; | ||
@@ -302,12 +416,12 @@ if (Array.isArray(item)) { | ||
} catch (err) { | ||
_didIteratorError2 = true; | ||
_iteratorError2 = err; | ||
_didIteratorError3 = true; | ||
_iteratorError3 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion2 && _iterator2.return) { | ||
_iterator2.return(); | ||
if (!_iteratorNormalCompletion3 && _iterator3.return) { | ||
_iterator3.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError2) { | ||
throw _iteratorError2; | ||
if (_didIteratorError3) { | ||
throw _iteratorError3; | ||
} | ||
@@ -322,2 +436,5 @@ } | ||
'properties': { type: 'property' }, | ||
'getters': { kind: 'get' }, | ||
'setters': { kind: 'set' }, | ||
'accessor-pairs': { accessorPair: true }, | ||
'static-properties': { type: 'property', static: true }, | ||
@@ -337,2 +454,6 @@ 'conventional-private-properties': { type: 'property', name: '/_.+/' }, | ||
return s.static === m.static; | ||
} }, { property: 'kind', value: 10, test: function test(m, s) { | ||
return s.kind === m.kind; | ||
} }, { property: 'accessorPair', value: 20, test: function test(m, s) { | ||
return s.accessorPair && m.matchingAccessor; | ||
} }]; |
{ | ||
"name": "eslint-plugin-sort-class-members", | ||
"version": "0.5.0", | ||
"version": "1.0.0", | ||
"description": "ESLint rule for enforcing consistent ES6 class member order.", | ||
@@ -38,4 +38,5 @@ "keywords": [ | ||
"devDependencies": { | ||
"@bryanrsmith/eslint-config-standard": "1.0.0", | ||
"babel-cli": "6.4.0", | ||
"babel-eslint": "5.0.0-beta6", | ||
"babel-eslint": "5.0.0", | ||
"babel-preset-es2015": "6.3.13", | ||
@@ -53,3 +54,3 @@ "babel-preset-stage-1": "6.3.13", | ||
}, | ||
"license": "ISC", | ||
"license": "MIT", | ||
"babel": { | ||
@@ -56,0 +57,0 @@ "presets": [ |
@@ -38,3 +38,4 @@ [![build status][travis-image]][travis-url] | ||
"[conventional-private-methods]" | ||
] | ||
], | ||
"accessorPairPositioning": "getThenSet", | ||
}] | ||
@@ -74,5 +75,6 @@ } | ||
The rule accepts three configuration properties: | ||
The rule accepts the following configuration properties: | ||
* `order`: Used to specify the expected sort order of class members. | ||
* `groups`: May optionally be used to created customized named groups of members so that `order` can be more easily maintained. Groups can be referenced by name by using square brackets. E.g., `"[group-name]"`. | ||
* `accessorPairPositioning`: Used to specify the required positioning of get/set pairs. Available values: `getThenSet`, `setThenGet`, `together`, `any`. | ||
* `stopAfterFirstProblem`: Only report the first sort problem in each class (plus the number of problems found). Useful if you only want to know that the class has sort problems without spamming error messages. The default is `false`. | ||
@@ -90,2 +92,3 @@ | ||
}, | ||
"accessorPairPositioning": "getThenSet", | ||
"stopAfterFirstProblem": false | ||
@@ -95,5 +98,7 @@ } | ||
Members can be matched by name (exact match or regexp), by type ("method" or "property"), and by whether or not the member is static. Each match may be described by an object with three properties, all of which are optional. | ||
* `name`: a string matching the name of the member. If the string starts and ends with `/` it will be interpreted as a regular expression. E.g., `"/_.+/"`. | ||
Members can be matched to positional slots using several criteria, including name (exact match or regexp), member type (method or property), and whether or not the member is static. Each match slot is described by an object with five properties, all of which are optional. | ||
* `name`: a string matching the name of the member. If the string starts and ends with `/` it will be interpreted as a regular expression. E.g., `"/_.+/"` will match members whose name starts with an underscore. | ||
* `type`: `"method"|"property"`. Note that class properties currently require a custom parser like [babel-eslint](https://github.com/babel/babel-eslint). | ||
* `kind`: `"get"|"set"`. A subtype of `type: "method"` that can match getter or setter methods. | ||
* `accessorPair`: `true|false`. True to match only getters and setters that are part of a pair. i.e., only those that have both `get` and `set` methods defined. | ||
* `static`: `true|false` to restrict the match to static or instance members. | ||
@@ -112,2 +117,5 @@ | ||
* `[properties]`: matches all properties | ||
* `[getters]`: matches all getter methods | ||
* `[setters]`: matches all setter methods | ||
* `[accessor-pairs]`: matches getters and setters that are part of a pair (where both `get` and `set` methods are defined) | ||
* `[static-properties]`: matches all static properties | ||
@@ -114,0 +122,0 @@ * `[conventional-private-properties]`: matches properties whose name starts with an underscore |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
22210
420
0
131
11