/** * @fileoverview ESLint rule to enforce sorted imports arrays in Angular @Component decorators. */ "use strict"; module.exports = { meta: { type: "suggestion", docs: { description: "Enforce sorted imports array in Angular @Component decorators", category: "Stylistic Issues", recommended: false, }, fixable: "code", schema: [] // no options }, create(context) { return { Decorator(node) { // Look for @Component decorator if ( node.expression && node.expression.callee && node.expression.callee.name === "Component" && node.expression.arguments && node.expression.arguments.length > 0 ) { const arg = node.expression.arguments[0]; if (arg && arg.type === "ObjectExpression") { // Find the 'imports' property const importsProperty = arg.properties.find( (prop) => prop.type === "Property" && ((prop.key.type === "Identifier" && prop.key.name === "imports") || (prop.key.type === "Literal" && prop.key.value === "imports")) ); if (importsProperty && importsProperty.value.type === "ArrayExpression") { const elements = importsProperty.value.elements; // Extract the names from the elements // Assuming elements are simple Identifiers or Literals const elementNames = elements.map((el) => { if (!el) return ""; if (el.type === "Identifier") { return el.name; } else if (el.type === "Literal" && typeof el.value === "string") { return el.value; } else { return ""; // Non-standard entry, skip sorting } }); // Check if sorted const sorted = [...elementNames].sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }) ); // Compare elementNames and sorted let isSorted = true; for (let i = 0; i < elementNames.length; i++) { if (elementNames[i] !== sorted[i]) { isSorted = false; break; } } if (!isSorted) { // Report and fix context.report({ node: importsProperty, message: "The imports array in @Component should be sorted alphabetically.", fix: (fixer) => { // Build a sorted array expression text const sourceCode = context.getSourceCode(); const sortedElements = []; for (const name of sorted) { // Attempt to find original element to preserve formatting if possible const originalElement = elements.find((el) => { if (!el) return false; if (el.type === "Identifier" && el.name === name) return true; if (el.type === "Literal" && el.value === name) return true; return false; }); if (originalElement) { sortedElements.push(sourceCode.getText(originalElement)); } else { // fallback: write identifier as is // If it's a string, wrap in quotes const isIdentifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name); sortedElements.push(isIdentifier ? name : `'${name}'`); } } const arrayText = `[${sortedElements.join(", ")}]`; const importsValue = importsProperty.value; return fixer.replaceTextRange( [importsValue.range[0], importsValue.range[1]], arrayText ); }, }); } } } } }, }; }, };