personnal/ecole/node_modules copy/docxtemplater/.eslint-rules/sortable.js

328 lines
8.1 KiB
JavaScript

const naturalCompare = require("natural-compare");
function intersectSafe(a, b) {
var ai = 0,
bi = 0;
var result = [];
while (ai < a.length && bi < b.length) {
if (a[ai] < b[bi]) {
ai++;
} else if (a[ai] > b[bi]) {
bi++;
} /* they're equal */ else {
result.push(a[ai]);
ai++;
bi++;
}
}
return result;
}
module.exports = {
meta: {
type: "suggestion",
fixable: "code",
docs: {
description: "require object keys to be sorted",
category: "Stylistic Issues",
recommended: false,
url: "https://github.com/namnm/eslint-plugin-sort-keys",
},
schema: [
{
enum: ["asc", "desc"],
},
{
type: "object",
properties: {
caseSensitive: {
type: "boolean",
default: true,
},
natural: {
type: "boolean",
default: false,
},
minKeys: {
type: "integer",
minimum: 2,
default: 2,
},
},
additionalProperties: false,
},
],
messages: {
sortKeys:
"Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",
},
},
create(ctx) {
// Parse options
const order = ctx.options[0] || "asc";
const options = ctx.options[1];
const insensitive = (options && options.caseSensitive) === false;
const natural = Boolean(options && options.natural);
const minKeys = Number(options && options.minKeys) || 2;
// The stack to save the previous property's name for each object literals
let stack = null;
// Shared SpreadElement for ExperimentalSpreadProperty
const SpreadElement = (node) => {
if (node.parent.type === "ObjectExpression") {
stack.prevName = null;
}
};
return {
ExperimentalSpreadProperty: SpreadElement,
SpreadElement,
ObjectExpression() {
stack = {
upper: stack,
prevName: null,
prevNode: null,
};
},
"ObjectExpression:exit"() {
stack = stack.upper;
},
Property(node) {
if (node.parent.type === "ObjectPattern") {
return;
}
if (node.parent.properties.length < minKeys) {
return;
}
function getNames(node) {
const { properties } = node;
const names = [];
properties.forEach(function (prop) {
if (prop.key && prop.key.name) {
names.push(prop.key.name);
}
});
return names;
}
const names = getNames(node.parent);
const sortedVars = [
[
"it",
"content",
"options",
"scope",
"error",
"errorType",
"resolved",
"result",
"xmllexed",
"lexed",
"parsed",
"postparsed",
],
["preparse", "parse", "postparse", "constructor"],
];
sortedVars.forEach(function (vars) {
const intersect = intersectSafe(names, vars);
if (intersect.length <= 1) {
return;
}
const prevName = stack.prevName;
const prevNode = stack.prevNode;
const thisName = getPropertyName(node);
if (thisName !== null) {
stack.prevName = thisName;
stack.prevNode = node || prevNode;
}
if (prevName === null || thisName === null) {
return;
}
const isValidOrder = function (prevName, thisName) {
const indexPrev = vars.indexOf(prevName);
const indexThis = vars.indexOf(thisName);
if (indexPrev === -1 || indexThis === -1) {
return true;
}
if (indexPrev < indexThis) {
return true;
}
return false;
};
if (!isValidOrder(prevName, thisName)) {
ctx.report({
node,
loc: node.key.loc,
messageId: "sortKeys",
data: {
thisName,
prevName,
order,
insensitive: insensitive ? "insensitive " : "",
natural: natural ? "natural " : "",
},
fix(fixer) {
// Check if already sorted
if (
node.parent.__alreadySorted ||
node.parent.properties.__alreadySorted
) {
return [];
}
node.parent.__alreadySorted = true;
node.parent.properties.__alreadySorted = true;
//
const src = ctx.getSourceCode();
const props = node.parent.properties;
// Split into parts on each spread operator (empty key)
const parts = [];
let part = [];
props.forEach((p) => {
if (!p.key) {
parts.push(part);
part = [];
} else {
part.push(p);
}
});
parts.push(part);
// Sort all parts
parts.forEach((part) => {
part.sort((p1, p2) => {
const n1 = getPropertyName(p1);
const n2 = getPropertyName(p2);
if (insensitive && n1.toLowerCase() === n2.toLowerCase()) {
return 0;
}
return isValidOrder(n1, n2) ? -1 : 1;
});
});
// Perform fixes
const fixes = [];
let newIndex = 0;
parts.forEach((part) => {
part.forEach((p) => {
moveProperty(p, props[newIndex], fixer, src).forEach((f) =>
fixes.push(f)
);
newIndex++;
});
newIndex++;
});
return fixes;
},
});
}
});
},
};
},
};
const moveProperty = (thisNode, toNode, fixer, src) => {
if (thisNode === toNode) {
return [];
}
const fixes = [];
// Move property
fixes.push(fixer.replaceText(toNode, src.getText(thisNode)));
// Move comments on top of this property, but do not move comments
// on the same line with the previous property
const prev = findTokenPrevLine(thisNode, src);
const cond = (c) => !prev || prev.loc.end.line !== c.loc.start.line;
const commentsBefore = src.getCommentsBefore(thisNode).filter(cond);
if (commentsBefore.length) {
const prevComments = src
.getCommentsBefore(thisNode)
.filter((c) => !cond(c));
const b = prevComments.length
? prevComments[prevComments.length - 1].range[1]
: prev
? prev.range[1]
: commentsBefore[0].range[0];
const e = commentsBefore[commentsBefore.length - 1].range[1];
fixes.push(fixer.replaceTextRange([b, e], ""));
const toPrev = src.getTokenBefore(toNode, { includeComments: true });
const txt = src.text.substring(b, e);
fixes.push(fixer.insertTextAfter(toPrev, txt));
}
// Move comments on the same line with this property
const next = findCommaSameLine(thisNode, src) || thisNode;
const commentsAfter = src
.getCommentsAfter(next)
.filter((c) => thisNode.loc.end.line === c.loc.start.line);
if (commentsAfter.length) {
const b = next.range[1];
const e = commentsAfter[commentsAfter.length - 1].range[1];
fixes.push(fixer.replaceTextRange([b, e], ""));
const toNext = findCommaSameLine(toNode, src) || toNode;
const txt = src.text.substring(b, e);
fixes.push(fixer.insertTextAfter(toNext, txt));
}
return fixes;
};
const findTokenPrevLine = (node, src) => {
let t = src.getTokenBefore(node);
while (true) {
if (!t || t.range[0] < node.parent.range[0]) {
return null;
}
if (t.loc.end.line < node.loc.start.line) {
return t;
}
t = src.getTokenBefore(t);
}
};
const findCommaSameLine = (node, src) => {
const t = src.getTokenAfter(node);
return t && t.value === "," && node.loc.end.line === t.loc.start.line
? t
: null;
};
const isValidOrders = {
asc: (a, b) => a <= b,
ascI: (a, b) => a.toLowerCase() <= b.toLowerCase(),
ascN: (a, b) => naturalCompare(a, b) <= 0,
ascIN: (a, b) => naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0,
desc: (a, b) => isValidOrders.asc(b, a),
descI: (a, b) => isValidOrders.ascI(b, a),
descN: (a, b) => isValidOrders.ascN(b, a),
descIN: (a, b) => isValidOrders.ascIN(b, a),
};
const getPropertyName = (node) => {
let prop;
switch (node && node.type) {
case "Property":
case "MethodDefinition":
prop = node.key;
break;
case "MemberExpression":
prop = node.property;
break;
}
switch (prop && prop.type) {
case "Literal":
return String(prop.value);
case "TemplateLiteral":
if (prop.expressions.length === 0 && prop.quasis.length === 1) {
return prop.quasis[0].value.cooked;
}
break;
case "Identifier":
if (!node.computed) {
return prop.name;
}
break;
}
return (node.key && node.key.name) || null;
};