303 lines
12 KiB
Plaintext
303 lines
12 KiB
Plaintext
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
const types_1 = require("@typescript-eslint/types");
|
||
|
const utils_1 = require("../utils");
|
||
|
const eslint_utils_1 = require("@eslint-community/eslint-utils");
|
||
|
const ast_utils_1 = require("../utils/ast-utils");
|
||
|
const string_literal_parser_1 = require("../utils/string-literal-parser");
|
||
|
const eslint_utils_2 = require("@eslint-community/eslint-utils");
|
||
|
const compat_1 = require("../utils/compat");
|
||
|
exports.default = (0, utils_1.createRule)("prefer-split-class-list", {
|
||
|
meta: {
|
||
|
docs: {
|
||
|
description: "require use split array elements in `class:list`",
|
||
|
category: "Stylistic Issues",
|
||
|
recommended: false,
|
||
|
},
|
||
|
schema: [
|
||
|
{
|
||
|
type: "object",
|
||
|
properties: {
|
||
|
splitLiteral: { type: "boolean" },
|
||
|
},
|
||
|
additionalProperties: false,
|
||
|
},
|
||
|
],
|
||
|
messages: {
|
||
|
uselessClsx: "Using `clsx()` for the `class:list` has no effect.",
|
||
|
split: "Can split elements with spaces.",
|
||
|
},
|
||
|
fixable: "code",
|
||
|
type: "suggestion",
|
||
|
},
|
||
|
create(context) {
|
||
|
var _a;
|
||
|
const sourceCode = (0, compat_1.getSourceCode)(context);
|
||
|
if (!sourceCode.parserServices.isAstro) {
|
||
|
return {};
|
||
|
}
|
||
|
const splitLiteral = Boolean((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.splitLiteral);
|
||
|
function shouldReport(state) {
|
||
|
if (state.isFirstElement) {
|
||
|
if (state.isLeading) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
if (state.isLastElement) {
|
||
|
if (state.isTrailing) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
if (splitLiteral) {
|
||
|
return true;
|
||
|
}
|
||
|
return state.isLeading || state.isTrailing;
|
||
|
}
|
||
|
function verifyAttr(attr) {
|
||
|
if ((0, ast_utils_1.getAttributeName)(attr) !== "class:list") {
|
||
|
return;
|
||
|
}
|
||
|
if (!attr.value ||
|
||
|
attr.value.type !== types_1.AST_NODE_TYPES.JSXExpressionContainer ||
|
||
|
attr.value.expression.type === types_1.AST_NODE_TYPES.JSXEmptyExpression) {
|
||
|
return;
|
||
|
}
|
||
|
const expression = attr.value.expression;
|
||
|
verifyExpression(expression, function* (fixer) {
|
||
|
if (expression.type === types_1.AST_NODE_TYPES.ArrayExpression) {
|
||
|
return;
|
||
|
}
|
||
|
yield fixer.insertTextBeforeRange(expression.range, "[");
|
||
|
yield fixer.insertTextAfterRange(expression.range, "]");
|
||
|
});
|
||
|
}
|
||
|
function verifyExpression(node, transformArray, call) {
|
||
|
if (node.type === types_1.AST_NODE_TYPES.TemplateLiteral) {
|
||
|
const first = node.quasis[0];
|
||
|
const last = node.quasis[node.quasis.length - 1];
|
||
|
for (const quasi of node.quasis) {
|
||
|
verifyTemplateElement(quasi, {
|
||
|
isFirstElement: first === quasi,
|
||
|
isLastElement: last === quasi,
|
||
|
transformArray,
|
||
|
call,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
else if (node.type === types_1.AST_NODE_TYPES.BinaryExpression) {
|
||
|
verifyBinaryExpression(node, transformArray);
|
||
|
}
|
||
|
else if (node.type === types_1.AST_NODE_TYPES.ArrayExpression) {
|
||
|
for (const element of node.elements) {
|
||
|
if (element) {
|
||
|
verifyExpression(element, transformArray);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (node.type === types_1.AST_NODE_TYPES.Literal) {
|
||
|
if (splitLiteral && (0, ast_utils_1.isStringLiteral)(node)) {
|
||
|
verifyStringLiteral(node, {
|
||
|
isFirstElement: true,
|
||
|
isLastElement: true,
|
||
|
transformArray,
|
||
|
call,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
else if (node.type === types_1.AST_NODE_TYPES.CallExpression) {
|
||
|
if (node.callee.type === types_1.AST_NODE_TYPES.MemberExpression &&
|
||
|
(0, eslint_utils_2.getPropertyName)(node.callee) === "trim") {
|
||
|
verifyExpression(node.callee.object, transformArray, ".trim()");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
function verifyTemplateElement(node, state) {
|
||
|
const stringEndOffset = node.tail ? node.range[1] - 1 : node.range[1] - 2;
|
||
|
let isLeading = true;
|
||
|
const spaces = [];
|
||
|
for (const ch of (0, string_literal_parser_1.parseStringTokens)(sourceCode.text, {
|
||
|
start: node.range[0] + 1,
|
||
|
end: stringEndOffset,
|
||
|
})) {
|
||
|
if (ch.value.trim()) {
|
||
|
if (spaces.length) {
|
||
|
if (shouldReport(Object.assign(Object.assign({}, state), { isLeading, isTrailing: false }))) {
|
||
|
reportRange([
|
||
|
spaces[0].range[0],
|
||
|
spaces[spaces.length - 1].range[1],
|
||
|
]);
|
||
|
}
|
||
|
spaces.length = 0;
|
||
|
}
|
||
|
isLeading = false;
|
||
|
}
|
||
|
else {
|
||
|
spaces.push(ch);
|
||
|
}
|
||
|
}
|
||
|
if (spaces.length) {
|
||
|
if (shouldReport(Object.assign(Object.assign({}, state), { isLeading, isTrailing: true }))) {
|
||
|
reportRange([spaces[0].range[0], spaces[spaces.length - 1].range[1]]);
|
||
|
}
|
||
|
spaces.length = 0;
|
||
|
}
|
||
|
function reportRange(range) {
|
||
|
context.report({
|
||
|
loc: {
|
||
|
start: sourceCode.getLocFromIndex(range[0]),
|
||
|
end: sourceCode.getLocFromIndex(range[1]),
|
||
|
},
|
||
|
messageId: "split",
|
||
|
*fix(fixer) {
|
||
|
yield* state.transformArray(fixer);
|
||
|
yield fixer.replaceTextRange(range, `\`${state.call || ""},\``);
|
||
|
},
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
function verifyBinaryExpression(node, transformArray) {
|
||
|
const elements = (0, ast_utils_1.extractConcatExpressions)(node, sourceCode);
|
||
|
if (elements == null) {
|
||
|
return;
|
||
|
}
|
||
|
const first = elements[0];
|
||
|
const last = elements[elements.length - 1];
|
||
|
for (const element of elements) {
|
||
|
if ((0, ast_utils_1.isStringLiteral)(element)) {
|
||
|
verifyStringLiteral(element, {
|
||
|
isFirstElement: first === element,
|
||
|
isLastElement: last === element,
|
||
|
transformArray,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
function verifyStringLiteral(node, state) {
|
||
|
const quote = sourceCode.text[node.range[0]];
|
||
|
let isLeading = true;
|
||
|
const spaces = [];
|
||
|
for (const ch of (0, string_literal_parser_1.parseStringTokens)(sourceCode.text, {
|
||
|
start: node.range[0] + 1,
|
||
|
end: node.range[1] - 1,
|
||
|
})) {
|
||
|
if (ch.value.trim()) {
|
||
|
if (spaces.length) {
|
||
|
if (shouldReport(Object.assign(Object.assign({}, state), { isLeading, isTrailing: false }))) {
|
||
|
reportRange([spaces[0].range[0], spaces[spaces.length - 1].range[1]], { isLeading, isTrailing: false });
|
||
|
}
|
||
|
spaces.length = 0;
|
||
|
}
|
||
|
isLeading = false;
|
||
|
}
|
||
|
else {
|
||
|
spaces.push(ch);
|
||
|
}
|
||
|
}
|
||
|
if (spaces.length) {
|
||
|
if (shouldReport(Object.assign(Object.assign({}, state), { isLeading, isTrailing: true }))) {
|
||
|
reportRange([spaces[0].range[0], spaces[spaces.length - 1].range[1]], { isLeading, isTrailing: true });
|
||
|
}
|
||
|
spaces.length = 0;
|
||
|
}
|
||
|
function reportRange(range, spaceState) {
|
||
|
context.report({
|
||
|
loc: {
|
||
|
start: sourceCode.getLocFromIndex(range[0]),
|
||
|
end: sourceCode.getLocFromIndex(range[1]),
|
||
|
},
|
||
|
messageId: "split",
|
||
|
*fix(fixer) {
|
||
|
yield* state.transformArray(fixer);
|
||
|
let leftQuote = quote;
|
||
|
let rightQuote = quote;
|
||
|
const bin = node.parent;
|
||
|
if (spaceState.isLeading &&
|
||
|
bin.right === node &&
|
||
|
isStringType(bin.left)) {
|
||
|
leftQuote = "";
|
||
|
}
|
||
|
if (spaceState.isTrailing &&
|
||
|
bin.left === node &&
|
||
|
isStringType(bin.right)) {
|
||
|
rightQuote = "";
|
||
|
}
|
||
|
const replaceRange = [...range];
|
||
|
if (!leftQuote || !rightQuote) {
|
||
|
if (!leftQuote) {
|
||
|
replaceRange[0]--;
|
||
|
}
|
||
|
if (!rightQuote) {
|
||
|
replaceRange[1]++;
|
||
|
}
|
||
|
yield fixer.remove(sourceCode.getTokensBetween(bin.left, bin.right, {
|
||
|
includeComments: false,
|
||
|
filter: (t) => t.value === bin.operator,
|
||
|
})[0]);
|
||
|
}
|
||
|
yield fixer.replaceTextRange(replaceRange, `${leftQuote}${state.call || ""},${rightQuote}`);
|
||
|
},
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
function verifyClsx(clsxCall) {
|
||
|
if (clsxCall.node.type !== types_1.AST_NODE_TYPES.CallExpression) {
|
||
|
return;
|
||
|
}
|
||
|
const callNode = clsxCall.node;
|
||
|
const parent = callNode.parent;
|
||
|
if (!parent ||
|
||
|
parent.type !== types_1.AST_NODE_TYPES.JSXExpressionContainer ||
|
||
|
parent.expression !== callNode) {
|
||
|
return;
|
||
|
}
|
||
|
const parentParent = parent.parent;
|
||
|
if (!parentParent ||
|
||
|
parentParent.type !== types_1.AST_NODE_TYPES.JSXAttribute ||
|
||
|
parentParent.value !== parent ||
|
||
|
(0, ast_utils_1.getAttributeName)(parentParent) !== "class:list") {
|
||
|
return;
|
||
|
}
|
||
|
context.report({
|
||
|
node: clsxCall.node.callee,
|
||
|
messageId: "uselessClsx",
|
||
|
*fix(fixer) {
|
||
|
const openToken = sourceCode.getTokenAfter(callNode.callee, {
|
||
|
includeComments: false,
|
||
|
filter: eslint_utils_1.isOpeningParenToken,
|
||
|
});
|
||
|
const closeToken = sourceCode.getLastToken(callNode);
|
||
|
yield fixer.removeRange([callNode.range[0], openToken.range[1]]);
|
||
|
yield fixer.remove(closeToken);
|
||
|
},
|
||
|
});
|
||
|
}
|
||
|
return {
|
||
|
Program(node) {
|
||
|
const sourceCode = (0, compat_1.getSourceCode)(context);
|
||
|
const referenceTracker = new eslint_utils_1.ReferenceTracker(sourceCode.getScope(node));
|
||
|
for (const call of referenceTracker.iterateEsmReferences({
|
||
|
clsx: {
|
||
|
[eslint_utils_1.ReferenceTracker.CALL]: true,
|
||
|
},
|
||
|
})) {
|
||
|
verifyClsx(call);
|
||
|
}
|
||
|
},
|
||
|
JSXAttribute: verifyAttr,
|
||
|
AstroTemplateLiteralAttribute: verifyAttr,
|
||
|
};
|
||
|
},
|
||
|
});
|
||
|
function isStringType(node) {
|
||
|
if (node.type === types_1.AST_NODE_TYPES.Literal) {
|
||
|
return typeof node.value === "string";
|
||
|
}
|
||
|
else if (node.type === types_1.AST_NODE_TYPES.TemplateLiteral) {
|
||
|
return true;
|
||
|
}
|
||
|
else if (node.type === types_1.AST_NODE_TYPES.BinaryExpression) {
|
||
|
return isStringType(node.left) || isStringType(node.right);
|
||
|
}
|
||
|
return (0, ast_utils_1.isStringCallExpression)(node);
|
||
|
}
|