280 lines
8.3 KiB
JavaScript
280 lines
8.3 KiB
JavaScript
'use strict';
|
|
|
|
const require_runtime = require('../_virtual/_rolldown/runtime.js');
|
|
const require_index = require('../utils/index.js');
|
|
|
|
//#region lib/rules/order-in-components.js
|
|
/**
|
|
* @fileoverview Keep order of properties in components
|
|
* @author Michał Sajnóg
|
|
*/
|
|
var require_order_in_components = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
|
|
const utils = require_index.default;
|
|
const traverseNodes = require("vue-eslint-parser").AST.traverseNodes;
|
|
/**
|
|
* @typedef {import('eslint-visitor-keys').VisitorKeys} VisitorKeys
|
|
*/
|
|
const defaultOrder = [
|
|
"el",
|
|
"name",
|
|
"key",
|
|
"parent",
|
|
"functional",
|
|
["delimiters", "comments"],
|
|
[
|
|
"components",
|
|
"directives",
|
|
"filters"
|
|
],
|
|
"extends",
|
|
"mixins",
|
|
["provide", "inject"],
|
|
"ROUTER_GUARDS",
|
|
"layout",
|
|
"middleware",
|
|
"validate",
|
|
"scrollToTop",
|
|
"transition",
|
|
"loading",
|
|
"inheritAttrs",
|
|
"model",
|
|
["props", "propsData"],
|
|
"emits",
|
|
"slots",
|
|
"expose",
|
|
"setup",
|
|
"asyncData",
|
|
"data",
|
|
"fetch",
|
|
"head",
|
|
"computed",
|
|
"watch",
|
|
"watchQuery",
|
|
"LIFECYCLE_HOOKS",
|
|
"methods",
|
|
["template", "render"],
|
|
"renderError"
|
|
];
|
|
/** @type { { [key: string]: string[] } } */
|
|
const groups = {
|
|
LIFECYCLE_HOOKS: [
|
|
"beforeCreate",
|
|
"created",
|
|
"beforeMount",
|
|
"mounted",
|
|
"beforeUpdate",
|
|
"updated",
|
|
"activated",
|
|
"deactivated",
|
|
"beforeUnmount",
|
|
"unmounted",
|
|
"beforeDestroy",
|
|
"destroyed",
|
|
"renderTracked",
|
|
"renderTriggered",
|
|
"errorCaptured"
|
|
],
|
|
ROUTER_GUARDS: [
|
|
"beforeRouteEnter",
|
|
"beforeRouteUpdate",
|
|
"beforeRouteLeave"
|
|
]
|
|
};
|
|
/**
|
|
* @param {(string | string[])[]} order
|
|
*/
|
|
function getOrderMap(order) {
|
|
/** @type {Map<string, number>} */
|
|
const orderMap = /* @__PURE__ */ new Map();
|
|
for (const [i, property] of order.entries()) if (Array.isArray(property)) for (const p of property) orderMap.set(p, i);
|
|
else orderMap.set(property, i);
|
|
return orderMap;
|
|
}
|
|
/**
|
|
* @param {Token} node
|
|
*/
|
|
function isComma(node) {
|
|
return node.type === "Punctuator" && node.value === ",";
|
|
}
|
|
const ARITHMETIC_OPERATORS = [
|
|
"+",
|
|
"-",
|
|
"*",
|
|
"/",
|
|
"%",
|
|
"**"
|
|
];
|
|
const BITWISE_OPERATORS = [
|
|
"&",
|
|
"|",
|
|
"^",
|
|
"~",
|
|
"<<",
|
|
">>",
|
|
">>>"
|
|
];
|
|
const COMPARISON_OPERATORS = [
|
|
"==",
|
|
"!=",
|
|
"===",
|
|
"!==",
|
|
">",
|
|
">=",
|
|
"<",
|
|
"<="
|
|
];
|
|
const RELATIONAL_OPERATORS = ["in", "instanceof"];
|
|
const ALL_BINARY_OPERATORS = new Set([
|
|
...ARITHMETIC_OPERATORS,
|
|
...BITWISE_OPERATORS,
|
|
...COMPARISON_OPERATORS,
|
|
...RELATIONAL_OPERATORS
|
|
]);
|
|
const LOGICAL_OPERATORS = new Set([
|
|
"&&",
|
|
"||",
|
|
"??"
|
|
]);
|
|
/**
|
|
* Result `true` if the node is sure that there are no side effects
|
|
*
|
|
* Currently known side effects types
|
|
*
|
|
* node.type === 'CallExpression'
|
|
* node.type === 'NewExpression'
|
|
* node.type === 'UpdateExpression'
|
|
* node.type === 'AssignmentExpression'
|
|
* node.type === 'TaggedTemplateExpression'
|
|
* node.type === 'UnaryExpression' && node.operator === 'delete'
|
|
*
|
|
* @param {ASTNode} node target node
|
|
* @param {VisitorKeys} visitorKeys sourceCode.visitorKey
|
|
* @returns {boolean} no side effects
|
|
*/
|
|
function isNotSideEffectsNode(node, visitorKeys) {
|
|
let result = true;
|
|
/** @type {ASTNode | null} */
|
|
let skipNode = null;
|
|
traverseNodes(node, {
|
|
visitorKeys,
|
|
enterNode(node) {
|
|
if (!result || skipNode) return;
|
|
if (node.type === "FunctionExpression" || node.type === "Identifier" || node.type === "Literal" || node.type === "ArrowFunctionExpression" || node.type === "TemplateElement" || node.type === "TSAsExpression") skipNode = node;
|
|
else if (node.type !== "Property" && node.type !== "ObjectExpression" && node.type !== "ArrayExpression" && (node.type !== "UnaryExpression" || ![
|
|
"!",
|
|
"~",
|
|
"+",
|
|
"-",
|
|
"typeof"
|
|
].includes(node.operator)) && (node.type !== "BinaryExpression" || !ALL_BINARY_OPERATORS.has(node.operator)) && (node.type !== "LogicalExpression" || !LOGICAL_OPERATORS.has(node.operator)) && node.type !== "MemberExpression" && node.type !== "ConditionalExpression" && node.type !== "SpreadElement" && node.type !== "TemplateLiteral" && node.type !== "ChainExpression") result = false;
|
|
},
|
|
leaveNode(node) {
|
|
if (skipNode === node) skipNode = null;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
module.exports = {
|
|
meta: {
|
|
type: "suggestion",
|
|
docs: {
|
|
description: "enforce order of properties in components",
|
|
categories: ["vue3-recommended", "vue2-recommended"],
|
|
url: "https://eslint.vuejs.org/rules/order-in-components.html"
|
|
},
|
|
fixable: "code",
|
|
hasSuggestions: true,
|
|
schema: [{
|
|
type: "object",
|
|
properties: { order: { type: "array" } },
|
|
additionalProperties: false
|
|
}],
|
|
messages: {
|
|
order: "The \"{{name}}\" property should be above the \"{{firstUnorderedPropertyName}}\" property on line {{line}}.",
|
|
reorderWithSideEffects: "Manually move \"{{name}}\" property above \"{{firstUnorderedPropertyName}}\" property on line {{line}} (might break side effects)."
|
|
}
|
|
},
|
|
create(context) {
|
|
const orderMap = getOrderMap(((context.options[0] || {}).order || defaultOrder).map((property) => typeof property === "string" && groups[property] || property));
|
|
const sourceCode = context.sourceCode;
|
|
/**
|
|
* @param {string} name
|
|
*/
|
|
function getOrderPosition(name) {
|
|
const num = orderMap.get(name);
|
|
return num == null ? -1 : num;
|
|
}
|
|
/**
|
|
* @param {RuleFixer} fixer
|
|
* @param {Property} propertyNode
|
|
* @param {Property} unorderedPropertyNode
|
|
*/
|
|
function* handleFix(fixer, propertyNode, unorderedPropertyNode) {
|
|
const afterComma = sourceCode.getTokenAfter(propertyNode);
|
|
const hasAfterComma = isComma(afterComma);
|
|
const beforeComma = sourceCode.getTokenBefore(propertyNode);
|
|
const codeStart = beforeComma.range[1];
|
|
const codeEnd = hasAfterComma ? afterComma.range[1] : propertyNode.range[1];
|
|
const removeStart = hasAfterComma ? codeStart : beforeComma.range[0];
|
|
yield fixer.removeRange([removeStart, codeEnd]);
|
|
const propertyCode = sourceCode.text.slice(codeStart, codeEnd) + (hasAfterComma ? "" : ",");
|
|
const insertTarget = sourceCode.getTokenBefore(unorderedPropertyNode);
|
|
yield fixer.insertTextAfter(insertTarget, propertyCode);
|
|
}
|
|
/**
|
|
* @param {(Property | SpreadElement)[]} propertiesNodes
|
|
*/
|
|
function checkOrder(propertiesNodes) {
|
|
const properties = propertiesNodes.filter(utils.isProperty).map((property) => ({
|
|
node: property,
|
|
name: utils.getStaticPropertyName(property) || property.key.type === "Identifier" && property.key.name || ""
|
|
}));
|
|
for (const [i, property] of properties.entries()) {
|
|
if (getOrderPosition(property.name) < 0) continue;
|
|
const firstUnorderedProperty = properties.slice(0, i).filter((p) => getOrderPosition(p.name) > getOrderPosition(property.name)).sort((p1, p2) => getOrderPosition(p1.name) > getOrderPosition(p2.name) ? 1 : -1)[0];
|
|
if (firstUnorderedProperty) {
|
|
const line = firstUnorderedProperty.node.loc.start.line;
|
|
const propertyNode = property.node;
|
|
const firstUnorderedPropertyNode = firstUnorderedProperty.node;
|
|
const hasSideEffectsPossibility = propertiesNodes.slice(propertiesNodes.indexOf(firstUnorderedPropertyNode), propertiesNodes.indexOf(propertyNode) + 1).some((property) => !isNotSideEffectsNode(property, sourceCode.visitorKeys));
|
|
context.report({
|
|
node: property.node,
|
|
messageId: "order",
|
|
data: {
|
|
name: property.name,
|
|
firstUnorderedPropertyName: firstUnorderedProperty.name,
|
|
line
|
|
},
|
|
fix: hasSideEffectsPossibility ? void 0 : (fixer) => handleFix(fixer, propertyNode, firstUnorderedPropertyNode),
|
|
suggest: hasSideEffectsPossibility ? [{
|
|
messageId: "reorderWithSideEffects",
|
|
data: {
|
|
name: property.name,
|
|
firstUnorderedPropertyName: firstUnorderedProperty.name,
|
|
line
|
|
},
|
|
fix: (fixer) => handleFix(fixer, propertyNode, firstUnorderedPropertyNode)
|
|
}] : void 0
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return utils.compositingVisitors(utils.executeOnVue(context, (obj) => {
|
|
checkOrder(obj.properties);
|
|
}), utils.defineScriptSetupVisitor(context, { onDefineOptionsEnter(node) {
|
|
if (node.arguments.length === 0) return;
|
|
const define = node.arguments[0];
|
|
if (define.type !== "ObjectExpression") return;
|
|
checkOrder(define.properties);
|
|
} }));
|
|
}
|
|
};
|
|
}));
|
|
|
|
//#endregion
|
|
Object.defineProperty(exports, 'default', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return require_order_in_components();
|
|
}
|
|
}); |