eslint-plugin-react-native-unistyles
Version:
React Native Unistyles rules for ESLint
178 lines (177 loc) • 9.82 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sortStyles = void 0;
const utils_1 = require("@typescript-eslint/utils");
const stylesheet_1 = require("../util/stylesheet");
const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/RodSarhan/eslint-plugin-react-native-unistyles/blob/main/docs/rules/${name}.md`);
exports.sortStyles = createRule({
name: 'sort-styles',
meta: {
docs: { description: 'Disallow unused styles' },
type: 'suggestion',
messages: {
expectedDifferentOrder: "Expected {{ type }} to be in {{ order }}ending order. '{{ currentName }}' should be before '{{ prevName }}'.",
},
fixable: 'code',
schema: [
{ type: 'string', enum: ['asc', 'desc'] },
{
type: 'object',
properties: { ignoreStyleNames: { type: 'boolean' }, ignoreStyleProperties: { type: 'boolean' } },
additionalProperties: false,
},
],
},
defaultOptions: ['asc', { ignoreStyleNames: false, ignoreStyleProperties: false }],
create: (context) => {
const order = context.options[0] || 'asc';
const options = context.options[1] || {};
const { ignoreStyleNames } = options;
const { ignoreStyleProperties } = options;
const isValidOrder = order === 'asc' ? (a, b) => a <= b : (a, b) => a >= b;
const sourceCode = context.sourceCode;
function sort(array) {
const result = [];
return result.concat(array).sort((a, b) => {
const identifierA = stylesheet_1.stylesASTHelpers.getStylePropertyIdentifier(a);
const identifierB = stylesheet_1.stylesASTHelpers.getStylePropertyIdentifier(b);
let sortOrder = 0;
if (identifierA && identifierB && stylesheet_1.stylesASTHelpers.isEitherShortHand(identifierA, identifierB)) {
return a.range[0] - b.range[0];
}
if (identifierA && identifierB) {
if (identifierA < identifierB) {
sortOrder = -1;
}
else if (identifierA > identifierB) {
sortOrder = 1;
}
}
return sortOrder * (order === 'asc' ? 1 : -1);
});
}
function report(args) {
const { array, type, node, prev, current } = args;
const currentName = stylesheet_1.stylesASTHelpers.getStylePropertyIdentifier(current);
const prevName = stylesheet_1.stylesASTHelpers.getStylePropertyIdentifier(prev);
const fixFunction = (fixer) => {
// Create sorted array with properties and their associated comments
const sortedArray = sort(array);
// Build property-comment pairs for original and sorted arrays
const originalPairs = array.map((prop) => ({
property: prop,
comments: sourceCode.getCommentsBefore(prop),
}));
const sortedPairs = sortedArray.map((prop) => {
const originalPair = originalPairs.find((pair) => pair.property === prop);
return originalPair || { property: prop, comments: [] };
});
// Generate fixes by replacing each property with its sorted counterpart
const fixes = [];
for (let i = 0; i < array.length; i++) {
const originalProp = array[i];
const sortedPair = sortedPairs[i];
if (!originalProp || !sortedPair) {
continue;
}
if (originalProp !== sortedPair.property) {
// Build replacement text with comments above the property
let replacementText = '';
// Add comments above the property
sortedPair.comments.forEach((comment) => {
// preserve indentation by getting the original property's indentation
const originalPropStartLine = sourceCode.getLocFromIndex(originalProp.range[0]).line;
const lines = sourceCode.getLines();
const lineText = lines[originalPropStartLine - 1];
const indentation = lineText?.match(/^(\s*)/)?.[1] || '';
replacementText += sourceCode.getText(comment) + '\n' + indentation;
});
// Add the property itself
replacementText += sourceCode.getText(sortedPair.property);
// Calculate the range to include comments above the original property
const commentsBeforeOriginal = sourceCode.getCommentsBefore(originalProp);
const startPos = commentsBeforeOriginal.length > 0 && commentsBeforeOriginal[0]
? commentsBeforeOriginal[0].range[0]
: originalProp.range[0];
fixes.push(fixer.replaceTextRange([startPos, originalProp.range[1]], replacementText));
}
}
return fixes;
};
context.report({
node,
messageId: 'expectedDifferentOrder',
data: { type, order, currentName, prevName },
loc: current.key.loc,
fix: fixFunction,
});
}
function checkIsSorted(args) {
const { array, type, node } = args;
for (let i = 1; i < array.length; i += 1) {
const prev = array[i - 1];
const current = array[i];
if (prev?.type !== utils_1.AST_NODE_TYPES.Property || current?.type !== utils_1.AST_NODE_TYPES.Property) {
return;
}
const prevName = stylesheet_1.stylesASTHelpers.getStylePropertyIdentifier(prev);
const currentName = stylesheet_1.stylesASTHelpers.getStylePropertyIdentifier(current);
const oneIsShorthandForTheOther = type === 'style-properties'
&& prevName
&& currentName
&& stylesheet_1.stylesASTHelpers.isEitherShortHand(prevName, currentName);
if (!oneIsShorthandForTheOther && prevName && currentName && !isValidOrder(prevName, currentName)) {
return report({ array, type, node, prev, current });
}
}
}
return {
CallExpression: function (node) {
if (!stylesheet_1.stylesASTHelpers.isStyleSheetDeclaration(node)) {
return;
}
const styleDeclarationsChunks = stylesheet_1.stylesASTHelpers.getStyleDeclarationsChunks(node);
if (!ignoreStyleNames) {
styleDeclarationsChunks.forEach((styleDeclarationChunk) => {
checkIsSorted({ array: styleDeclarationChunk, type: 'style-names', node });
});
}
if (ignoreStyleProperties)
return;
styleDeclarationsChunks.forEach((styleDeclarationChunk) => {
styleDeclarationChunk.forEach((styleChunck) => {
let innerStyleProperties = [];
if (styleChunck.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
innerStyleProperties = styleChunck.value.properties;
}
if (styleChunck.value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
if (styleChunck.value.body.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
innerStyleProperties = styleChunck.value.body.properties;
}
if (styleChunck.value.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
const returnStatement = styleChunck.value.body.body.find((statement) => statement.type === utils_1.AST_NODE_TYPES.ReturnStatement);
if (returnStatement?.argument?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
innerStyleProperties = returnStatement.argument.properties;
}
}
}
if (styleChunck.value.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
const returnStatement = styleChunck.value.body.body.find((statement) => statement.type === utils_1.AST_NODE_TYPES.ReturnStatement);
if (returnStatement?.argument?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
innerStyleProperties = returnStatement.argument.properties;
}
}
if (innerStyleProperties.length < 2) {
return;
}
const stylePropertyChunks = stylesheet_1.stylesASTHelpers.getPropertiesChunks(innerStyleProperties);
stylePropertyChunks.forEach((stylePropertyChunk) => {
checkIsSorted({ array: stylePropertyChunk, type: 'style-properties', node });
});
return;
});
});
},
};
},
});