#!/usr/bin/env node /** * Angular Template Property Sorter * Reorders element properties after Prettier formatting * Order: outputs → two-way → inputs → attributes */ const fs = require('fs'); const path = require('path'); // Property type detection - lower number = higher priority (comes first) function getPropertyType(attrName) { if (attrName.match(/^\(/)) return 0; // (output)="..." → outputs first if (attrName.match(/^\[\(/)) return 1; // [(twoWay)]="..." → two-way second if (attrName.match(/^\[class\./)) return 2; // [class.x]="..." → inputs third if (attrName.match(/^\[attr\./)) return 2; // [attr.x]="..." → inputs third if (attrName.match(/^\[[\w\-]+\]/)) return 2; // [input]="..." → inputs third if (attrName.match(/^#/)) return 1.5; // #ref → template reference if (attrName.match(/^@/)) return -1; // @structural → should not appear here return 3; // everything else → attributes last } // Extract attribute name (before =) function getAttributeName(attrString) { const match = attrString.match(/^([^\s=]+)/); return match ? match[1] : attrString; } // Sort attributes by type function sortAttributes(attributes) { return attributes.sort((a, b) => { const nameA = getAttributeName(a); const nameB = getAttributeName(b); return getPropertyType(nameA) - getPropertyType(nameB); }); } // Format file - simple approach: parse and rebuild with sorted attrs function formatFile(filePath) { try { let content = fs.readFileSync(filePath, 'utf-8'); // Pattern: match multi-line elements with attributes // This is a conservative approach - only reorders complete multi-line attribute blocks const multiLineAttrRegex = /(<\w+[\w:\-]*)\n((?:\s+[^\s>]+(?:="[^"]*")?\n)*\s+[^\s>]+(?:="[^"]*")?)\n(\s*>)/g; let modified = false; const result = content.replace(multiLineAttrRegex, (match, openTag, attrs, closeTag) => { // Get the indentation from the first line const lines = match.split('\n'); const firstLineIndent = lines[0].match(/^(\s*) line.trim()) .filter(line => line.length > 0); // Sort attributes const sorted = sortAttributes(attrLines); // Check if we changed the order if (sorted.join('\n') !== attrLines.join('\n')) { modified = true; } // Rebuild return openTag + '\n' + sorted.map(attr => attrIndent + attr).join('\n') + '\n' + closeTag; }); if (modified) { fs.writeFileSync(filePath, result, 'utf-8'); console.log(`✓ Sorted properties: ${filePath}`); return true; } return false; } catch (error) { console.error(`✗ Error processing ${filePath}:`, error.message); return false; } } // Main const args = process.argv.slice(2); function walkDir(dir, callback) { const files = fs.readdirSync(dir); files.forEach(file => { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory() && !filePath.includes('node_modules')) { walkDir(filePath, callback); } else if (file.endsWith('.html')) { callback(filePath); } }); } let processed = 0; if (args.length > 0) { args.forEach(file => { const fullPath = path.resolve(process.cwd(), file); if (fs.existsSync(fullPath)) { if (formatFile(fullPath)) { processed++; } } }); } else { walkDir(path.join(process.cwd(), 'toju-app/src/app'), (filePath) => { if (formatFile(filePath)) { processed++; } }); if (processed > 0) { console.log(`\nTotal files with properties sorted: ${processed}`); } } process.exit(0);