aboutsummaryrefslogtreecommitdiff
path: root/node_modules/tslint/lib/rules/memberOrderingRule.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/tslint/lib/rules/memberOrderingRule.js')
-rw-r--r--node_modules/tslint/lib/rules/memberOrderingRule.js204
1 files changed, 190 insertions, 14 deletions
diff --git a/node_modules/tslint/lib/rules/memberOrderingRule.js b/node_modules/tslint/lib/rules/memberOrderingRule.js
index e2e35a548..a9752e3a4 100644
--- a/node_modules/tslint/lib/rules/memberOrderingRule.js
+++ b/node_modules/tslint/lib/rules/memberOrderingRule.js
@@ -17,6 +17,7 @@
*/
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
+var tsutils_1 = require("tsutils");
var ts = require("typescript");
var error_1 = require("../error");
var Lint = require("../index");
@@ -96,7 +97,7 @@ var allMemberKindNames = utils_1.mapDefined(Object.keys(MemberKind), function (k
function namesMarkdown(names) {
return names.map(function (name) { return "* `" + name + "`"; }).join("\n ");
}
-var optionsDescription = (_a = ["\n One argument, which is an object, must be provided. It should contain an `order` property.\n The `order` property should have a value of one of the following strings:\n\n ", "\n\n Alternatively, the value for `order` maybe be an array consisting of the following strings:\n\n ", "\n\n You can also omit the access modifier to refer to \"public-\", \"protected-\", and \"private-\" all at once; for example, \"static-field\".\n\n You can also make your own categories by using an object instead of a string:\n\n {\n \"name\": \"static non-private\",\n \"kinds\": [\n \"public-static-field\",\n \"protected-static-field\",\n \"public-static-method\",\n \"protected-static-method\"\n ]\n }\n\n The '", "' option will enforce that members within the same category should be alphabetically sorted by name."], _a.raw = ["\n One argument, which is an object, must be provided. It should contain an \\`order\\` property.\n The \\`order\\` property should have a value of one of the following strings:\n\n ", "\n\n Alternatively, the value for \\`order\\` maybe be an array consisting of the following strings:\n\n ", "\n\n You can also omit the access modifier to refer to \"public-\", \"protected-\", and \"private-\" all at once; for example, \"static-field\".\n\n You can also make your own categories by using an object instead of a string:\n\n {\n \"name\": \"static non-private\",\n \"kinds\": [\n \"public-static-field\",\n \"protected-static-field\",\n \"public-static-method\",\n \"protected-static-method\"\n ]\n }\n\n The '", "' option will enforce that members within the same category should be alphabetically sorted by name."], Lint.Utils.dedent(_a, namesMarkdown(PRESET_NAMES), namesMarkdown(allMemberKindNames), OPTION_ALPHABETIZE));
+var optionsDescription = Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n One argument, which is an object, must be provided. It should contain an `order` property.\n The `order` property should have a value of one of the following strings:\n\n ", "\n\n Alternatively, the value for `order` maybe be an array consisting of the following strings:\n\n ", "\n\n You can also omit the access modifier to refer to \"public-\", \"protected-\", and \"private-\" all at once; for example, \"static-field\".\n\n You can also make your own categories by using an object instead of a string:\n\n {\n \"name\": \"static non-private\",\n \"kinds\": [\n \"public-static-field\",\n \"protected-static-field\",\n \"public-static-method\",\n \"protected-static-method\"\n ]\n }\n\n The '", "' option will enforce that members within the same category should be alphabetically sorted by name."], ["\n One argument, which is an object, must be provided. It should contain an \\`order\\` property.\n The \\`order\\` property should have a value of one of the following strings:\n\n ", "\n\n Alternatively, the value for \\`order\\` maybe be an array consisting of the following strings:\n\n ", "\n\n You can also omit the access modifier to refer to \"public-\", \"protected-\", and \"private-\" all at once; for example, \"static-field\".\n\n You can also make your own categories by using an object instead of a string:\n\n {\n \"name\": \"static non-private\",\n \"kinds\": [\n \"public-static-field\",\n \"protected-static-field\",\n \"public-static-method\",\n \"protected-static-method\"\n ]\n }\n\n The '", "' option will enforce that members within the same category should be alphabetically sorted by name."])), namesMarkdown(PRESET_NAMES), namesMarkdown(allMemberKindNames), OPTION_ALPHABETIZE);
var Rule = /** @class */ (function (_super) {
tslib_1.__extends(Rule, _super);
function Rule() {
@@ -124,7 +125,8 @@ var Rule = /** @class */ (function (_super) {
Rule.metadata = {
ruleName: "member-ordering",
description: "Enforces member ordering.",
- rationale: "A consistent ordering for class members can make classes easier to read, navigate, and edit.",
+ hasFix: true,
+ rationale: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n A consistent ordering for class members can make classes easier to read, navigate, and edit.\n\n A common opposite practice to `member-ordering` is to keep related groups of classes together.\n Instead of creating classes with multiple separate groups, consider splitting class responsibilities\n apart across multiple single-responsibility classes.\n "], ["\n A consistent ordering for class members can make classes easier to read, navigate, and edit.\n\n A common opposite practice to \\`member-ordering\\` is to keep related groups of classes together.\n Instead of creating classes with multiple separate groups, consider splitting class responsibilities\n apart across multiple single-responsibility classes.\n "]))),
optionsDescription: optionsDescription,
options: {
type: "object",
@@ -187,11 +189,15 @@ exports.Rule = Rule;
var MemberOrderingWalker = /** @class */ (function (_super) {
tslib_1.__extends(MemberOrderingWalker, _super);
function MemberOrderingWalker() {
- return _super !== null && _super.apply(this, arguments) || this;
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.fixes = [];
+ return _this;
}
MemberOrderingWalker.prototype.walk = function (sourceFile) {
var _this = this;
var cb = function (node) {
+ // NB: iterate through children first!
+ ts.forEachChild(node, cb);
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.ClassExpression:
@@ -199,13 +205,27 @@ var MemberOrderingWalker = /** @class */ (function (_super) {
case ts.SyntaxKind.TypeLiteral:
_this.checkMembers(node.members);
}
- return ts.forEachChild(node, cb);
};
- return ts.forEachChild(sourceFile, cb);
+ ts.forEachChild(sourceFile, cb);
+ // assign Replacements which have not been merged into surrounding ones to their RuleFailures.
+ this.fixes.forEach(function (_a) {
+ var failure = _a[0], replacement = _a[1];
+ failure.getFix().push(replacement);
+ });
};
+ /**
+ * Check wether the passed members adhere to the configured order. If not, RuleFailures are generated and a single
+ * Lint.Replacement is generated, which replaces the entire NodeArray with a correctly sorted one. The Replacement
+ * is not immediately added to a RuleFailure, as incorrectly sorted nodes can be nested (e.g. a class declaration
+ * in a method implementation), but instead temporarily stored in `this.fixes`. Nested Replacements are manually
+ * merged, as TSLint doesn't handle overlapping ones. For this reason it is important that the recursion happens
+ * before the checkMembers call in this.walk().
+ */
MemberOrderingWalker.prototype.checkMembers = function (members) {
+ var _this = this;
var prevRank = -1;
var prevName;
+ var failureExists = false;
for (var _i = 0, members_1 = members; _i < members_1.length; _i++) {
var member = members_1[_i];
var rank = this.memberRank(member);
@@ -222,7 +242,9 @@ var MemberOrderingWalker = /** @class */ (function (_super) {
: "at the beginning of the class/interface";
var errorLine1 = "Declaration of " + nodeType + " not allowed after declaration of " + prevNodeType + ". " +
("Instead, this should come " + locationHint + ".");
- this.addFailureAtNode(member, errorLine1);
+ // add empty array as fix so we can add a replacement later. (fix itself is readonly)
+ this.addFailureAtNode(member, errorLine1, []);
+ failureExists = true;
}
else {
if (this.options.alphabetize && member.name !== undefined) {
@@ -232,7 +254,8 @@ var MemberOrderingWalker = /** @class */ (function (_super) {
}
var curName = nameString(member.name);
if (prevName !== undefined && caseInsensitiveLess(curName, prevName)) {
- this.addFailureAtNode(member.name, Rule.FAILURE_STRING_ALPHABETIZE(this.findLowerName(members, rank, curName), curName));
+ this.addFailureAtNode(member.name, Rule.FAILURE_STRING_ALPHABETIZE(this.findLowerName(members, rank, curName), curName), []);
+ failureExists = true;
}
else {
prevName = curName;
@@ -242,6 +265,56 @@ var MemberOrderingWalker = /** @class */ (function (_super) {
prevRank = rank;
}
}
+ if (failureExists) {
+ var sortedMemberIndexes = members.map(function (_, i) { return i; }).sort(function (ai, bi) {
+ var a = members[ai];
+ var b = members[bi];
+ // first, sort by member rank
+ var rankDiff = _this.memberRank(a) - _this.memberRank(b);
+ if (rankDiff !== 0) {
+ return rankDiff;
+ }
+ // then lexicographically if alphabetize == true
+ if (_this.options.alphabetize && a.name !== undefined && b.name !== undefined) {
+ var aName = nameString(a.name);
+ var bName = nameString(b.name);
+ var nameDiff = aName.localeCompare(bName);
+ if (nameDiff !== 0) {
+ return nameDiff;
+ }
+ }
+ // finally, sort by position in original NodeArray so the sort remains stable.
+ return ai - bi;
+ });
+ var splits_1 = getSplitIndexes(members, this.sourceFile.text);
+ var sortedMembersText = sortedMemberIndexes.map(function (i) {
+ var start = splits_1[i];
+ var end = splits_1[i + 1];
+ var nodeText = _this.sourceFile.text.substring(start, end);
+ while (true) {
+ // check if there are previous fixes which we need to merge into this one
+ // if yes, remove it from the list so that we do not return overlapping Replacements
+ var fixIndex = arrayFindLastIndex(_this.fixes, function (_a) {
+ var r = _a[1];
+ return r.start >= start && r.start + r.length <= end;
+ });
+ if (fixIndex === -1) {
+ break;
+ }
+ var fix = _this.fixes.splice(fixIndex, 1)[0];
+ var replacement = fix[1];
+ nodeText = applyReplacementOffset(nodeText, replacement, start);
+ }
+ return nodeText;
+ });
+ // instead of assigning the fix immediately to the last failure, we temporarily store it in `this.fixes`,
+ // in case a containing node needs to be fixed too. We only "add" the fix to the last failure, although
+ // it fixes all failures in this NodeArray, as TSLint doesn't handle duplicate Replacements.
+ this.fixes.push([
+ arrayLast(this.failures),
+ Lint.Replacement.replaceFromTo(splits_1[0], arrayLast(splits_1), sortedMembersText.join("")),
+ ]);
+ }
};
/** Finds the lowest name higher than 'targetName'. */
MemberOrderingWalker.prototype.findLowerName = function (members, targetRank, targetName) {
@@ -303,8 +376,8 @@ function memberKindFromName(name) {
}
}
function getMemberKind(member) {
- var accessLevel = hasModifier(ts.SyntaxKind.PrivateKeyword) ? "private"
- : hasModifier(ts.SyntaxKind.ProtectedKeyword) ? "protected"
+ var accessLevel = tsutils_1.hasModifier(member.modifiers, ts.SyntaxKind.PrivateKeyword) ? "private"
+ : tsutils_1.hasModifier(member.modifiers, ts.SyntaxKind.ProtectedKeyword) ? "protected"
: "public";
switch (member.kind) {
case ts.SyntaxKind.Constructor:
@@ -320,12 +393,9 @@ function getMemberKind(member) {
return undefined;
}
function methodOrField(isMethod) {
- var membership = hasModifier(ts.SyntaxKind.StaticKeyword) ? "Static" : "Instance";
+ var membership = tsutils_1.hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword) ? "Static" : "Instance";
return memberKindForMethodOrField(accessLevel, membership, isMethod ? "Method" : "Field");
}
- function hasModifier(kind) {
- return Lint.hasModifier(member.modifiers, kind);
- }
}
var MemberCategory = /** @class */ (function () {
function MemberCategory(name, kinds) {
@@ -436,4 +506,110 @@ function nameString(name) {
return "";
}
}
-var _a;
+/**
+ * Returns the last element of an array. (Or undefined).
+ */
+function arrayLast(array) {
+ return array[array.length - 1];
+}
+/**
+ * Array.prototype.findIndex, but the last index.
+ */
+function arrayFindLastIndex(array, predicate) {
+ for (var i = array.length; i-- > 0;) {
+ if (predicate(array[i], i, array)) {
+ return i;
+ }
+ }
+ return -1;
+}
+/**
+ * Applies a Replacement to a part of the text which starts at offset.
+ * See also Replacement.apply
+ */
+function applyReplacementOffset(content, replacement, offset) {
+ return content.substring(0, replacement.start - offset)
+ + replacement.text
+ + content.substring(replacement.start - offset + replacement.length);
+}
+/**
+ * Get the indexes of the boundaries between nodes in the node array. The following points must be taken into account:
+ * - Trivia should stay with its corresponding node (comments on the same line following the token belong to the
+ * previous token, the rest to the next).
+ * - Reordering the subtexts should not result in code being commented out due to being moved between a "//" and
+ * the following newline.
+ * - The end of one node must be the start of the next, otherwise the intravening whitespace will be lost when
+ * reordering.
+ *
+ * Hence, the boundaries are chosen to be _after_ the newline following the node, or the beginning of the next token,
+ * if that comes first.
+ */
+function getSplitIndexes(members, text) {
+ var result = members.map(function (member) { return getNextSplitIndex(text, member.getFullStart()); });
+ result.push(getNextSplitIndex(text, arrayLast(members).getEnd()));
+ return result;
+}
+/**
+ * Calculates the index after the newline following pos, or the beginning of the next token, whichever comes first.
+ * See also getSplitIndexes.
+ * This method is a modified version of TypeScript's internal iterateCommentRanges function.
+ */
+function getNextSplitIndex(text, pos) {
+ scan: while (pos >= 0 && pos < text.length) {
+ var ch = text.charCodeAt(pos);
+ switch (ch) {
+ case 13 /* carriageReturn */:
+ if (text.charCodeAt(pos + 1) === 10 /* lineFeed */) {
+ pos++;
+ }
+ // falls through
+ case 10 /* lineFeed */:
+ pos++;
+ // split is after new line
+ return pos;
+ case 9 /* tab */:
+ case 11 /* verticalTab */:
+ case 12 /* formFeed */:
+ case 32 /* space */:
+ // skip whitespace
+ pos++;
+ continue;
+ case 47 /* slash */:
+ var nextChar = text.charCodeAt(pos + 1);
+ if (nextChar === 47 /* slash */ || nextChar === 42 /* asterisk */) {
+ var isSingleLineComment = nextChar === 47 /* slash */;
+ pos += 2;
+ if (isSingleLineComment) {
+ while (pos < text.length) {
+ if (ts.isLineBreak(text.charCodeAt(pos))) {
+ // the comment ends here, go back to default logic to handle parsing new line and result
+ continue scan;
+ }
+ pos++;
+ }
+ }
+ else {
+ while (pos < text.length) {
+ if (text.charCodeAt(pos) === 42 /* asterisk */ && text.charCodeAt(pos + 1) === 47 /* slash */) {
+ pos += 2;
+ continue scan;
+ }
+ pos++;
+ }
+ }
+ // if we arrive here, it's because pos == text.length
+ return pos;
+ }
+ break scan;
+ default:
+ // skip whitespace:
+ if (ch > 127 /* maxAsciiCharacter */ && (ts.isWhiteSpaceLike(ch))) {
+ pos++;
+ continue;
+ }
+ break scan;
+ }
+ }
+ return pos;
+}
+var templateObject_1, templateObject_2;