252 lines
9.9 KiB
JavaScript
252 lines
9.9 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const utils_1 = require("../utils");
|
|
const ast_utils_1 = require("../utils/ast-utils");
|
|
const compat_1 = require("../utils/compat");
|
|
exports.default = (0, utils_1.createRule)('prefer-class-directive', {
|
|
meta: {
|
|
docs: {
|
|
description: 'require class directives instead of ternary expressions',
|
|
category: 'Stylistic Issues',
|
|
recommended: false,
|
|
conflictWithPrettier: false
|
|
},
|
|
fixable: 'code',
|
|
schema: [],
|
|
messages: {
|
|
unexpected: 'Unexpected class using the ternary operator.'
|
|
},
|
|
type: 'suggestion'
|
|
},
|
|
create(context) {
|
|
const sourceCode = (0, compat_1.getSourceCode)(context);
|
|
function parseConditionalExpression(node) {
|
|
const result = new Map();
|
|
if (!processItems({
|
|
node: node.test
|
|
}, node.consequent)) {
|
|
return null;
|
|
}
|
|
if (!processItems({
|
|
not: true,
|
|
node: node.test
|
|
}, node.alternate)) {
|
|
return null;
|
|
}
|
|
return result;
|
|
function processItems(key, e) {
|
|
if (e.type === 'ConditionalExpression') {
|
|
const sub = parseConditionalExpression(e);
|
|
if (sub == null) {
|
|
return false;
|
|
}
|
|
for (const [expr, str] of sub) {
|
|
result.set({
|
|
...key,
|
|
chains: expr
|
|
}, str);
|
|
}
|
|
}
|
|
else {
|
|
const str = (0, ast_utils_1.getStringIfConstant)(e);
|
|
if (str == null) {
|
|
return false;
|
|
}
|
|
result.set(key, str);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
function exprToString({ node, not }) {
|
|
let text = sourceCode.text.slice(...node.range);
|
|
if (not) {
|
|
if (node.type === 'BinaryExpression') {
|
|
if (node.operator === '===' ||
|
|
node.operator === '==' ||
|
|
node.operator === '!==' ||
|
|
node.operator === '!=') {
|
|
const left = sourceCode.text.slice(...node.left.range);
|
|
const op = sourceCode.text.slice(node.left.range[1], node.right.range[0]);
|
|
const right = sourceCode.text.slice(...node.right.range);
|
|
return `${left}${node.operator === '===' || node.operator === '=='
|
|
? op.replace(/[=](={1,2})/g, '!$1')
|
|
: op.replace(/!(={1,2})/g, '=$1')}${right}`;
|
|
}
|
|
}
|
|
else if (node.type === 'UnaryExpression') {
|
|
if (node.operator === '!' && node.prefix) {
|
|
return sourceCode.text.slice(...node.argument.range);
|
|
}
|
|
}
|
|
if ((0, ast_utils_1.needParentheses)(node, 'not')) {
|
|
text = `(${text})`;
|
|
}
|
|
text = `!${text}`;
|
|
}
|
|
return text;
|
|
}
|
|
function getStrings(node) {
|
|
if (node.type === 'SvelteLiteral') {
|
|
return [node.value];
|
|
}
|
|
if (node.expression.type === 'ConditionalExpression') {
|
|
const values = parseConditionalExpression(node.expression);
|
|
if (values == null) {
|
|
return null;
|
|
}
|
|
return [...values.values()];
|
|
}
|
|
const str = (0, ast_utils_1.getStringIfConstant)(node.expression);
|
|
if (str == null) {
|
|
return null;
|
|
}
|
|
return [str];
|
|
}
|
|
function endsWithNonWord(node, index) {
|
|
for (let i = index; i >= 0; i--) {
|
|
const valueNode = node.value[i];
|
|
const strings = getStrings(valueNode);
|
|
if (strings == null) {
|
|
return false;
|
|
}
|
|
for (const str of strings) {
|
|
if (str) {
|
|
return !str[str.length - 1].trim();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function startsWithNonWord(node, index) {
|
|
for (let i = index; i < node.value.length; i++) {
|
|
const valueNode = node.value[i];
|
|
const strings = getStrings(valueNode);
|
|
if (strings == null) {
|
|
return false;
|
|
}
|
|
for (const str of strings) {
|
|
if (str) {
|
|
return !str[0].trim();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function report(node, map, attr) {
|
|
context.report({
|
|
node,
|
|
messageId: 'unexpected',
|
|
*fix(fixer) {
|
|
const classDirectives = [];
|
|
let space = ' ';
|
|
for (const [expr, className] of map) {
|
|
const trimmedClassName = className.trim();
|
|
if (trimmedClassName) {
|
|
classDirectives.push(`class:${trimmedClassName}={${exprToString(expr)}}`);
|
|
}
|
|
else {
|
|
space = className;
|
|
}
|
|
}
|
|
const fixesBuffer = [];
|
|
const index = attr.value.indexOf(node);
|
|
const beforeAttrValues = attr.value.slice(0, index);
|
|
const afterAttrValues = attr.value.slice(index + 1);
|
|
let valueNode;
|
|
while ((valueNode = beforeAttrValues[beforeAttrValues.length - 1])) {
|
|
if (valueNode.type === 'SvelteLiteral') {
|
|
if (!valueNode.value.trim()) {
|
|
beforeAttrValues.pop();
|
|
fixesBuffer.push(fixer.remove(valueNode));
|
|
continue;
|
|
}
|
|
if (valueNode.value.trimEnd() !== valueNode.value) {
|
|
fixesBuffer.push(fixer.replaceText(valueNode, valueNode.value.trimEnd()));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
while ((valueNode = afterAttrValues[0])) {
|
|
if (valueNode.type === 'SvelteLiteral') {
|
|
if (!valueNode.value.trim()) {
|
|
afterAttrValues.shift();
|
|
fixesBuffer.push(fixer.remove(valueNode));
|
|
continue;
|
|
}
|
|
if (valueNode.value.trimStart() !== valueNode.value) {
|
|
fixesBuffer.push(fixer.replaceText(valueNode, valueNode.value.trimStart()));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (!beforeAttrValues.length && !afterAttrValues.length) {
|
|
yield fixer.replaceText(attr, classDirectives.join(' '));
|
|
}
|
|
else {
|
|
yield* fixesBuffer;
|
|
if (beforeAttrValues.length && afterAttrValues.length) {
|
|
yield fixer.replaceText(node, space || ' ');
|
|
}
|
|
else {
|
|
yield fixer.remove(node);
|
|
}
|
|
yield fixer.insertTextAfterRange([attr.range[1], attr.range[1]], ` ${classDirectives.join(' ')}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function verify(node, index, attr) {
|
|
if (node.expression.type !== 'ConditionalExpression') {
|
|
return;
|
|
}
|
|
const map = parseConditionalExpression(node.expression);
|
|
if (map == null) {
|
|
return;
|
|
}
|
|
if (map.size > 2) {
|
|
return;
|
|
}
|
|
const prevIsWord = !startsWithNonWord(attr, index + 1);
|
|
const nextIsWord = !endsWithNonWord(attr, index - 1);
|
|
let canTransform = true;
|
|
for (const className of map.values()) {
|
|
if (className) {
|
|
if (!/^[\w-]*$/u.test(className.trim())) {
|
|
canTransform = false;
|
|
break;
|
|
}
|
|
if ((className[0].trim() && prevIsWord) ||
|
|
(className[className.length - 1].trim() && nextIsWord)) {
|
|
canTransform = false;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if (prevIsWord && nextIsWord) {
|
|
canTransform = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!canTransform) {
|
|
return;
|
|
}
|
|
report(node, map, attr);
|
|
}
|
|
return {
|
|
'SvelteStartTag > SvelteAttribute'(node) {
|
|
if (!(0, ast_utils_1.isHTMLElementLike)(node.parent.parent) || node.key.name !== 'class') {
|
|
return;
|
|
}
|
|
for (let index = 0; index < node.value.length; index++) {
|
|
const valueElement = node.value[index];
|
|
if (valueElement.type !== 'SvelteMustacheTag') {
|
|
continue;
|
|
}
|
|
verify(valueElement, index, node);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
});
|