lwc-linter
Version:
A comprehensive CLI tool for linting Lightning Web Components v8.0.0+ with modern LWC patterns, decorators, lifecycle hooks, and Salesforce platform integration
276 lines • 13.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadAppleDesignRules = loadAppleDesignRules;
function loadAppleDesignRules() {
return [
{
name: 'apple-spacing',
description: 'Enforce Apple design system spacing guidelines',
category: 'apple-design',
severity: 'warn',
fixable: true,
check: (content, filePath, config) => {
const issues = [];
if (!filePath.endsWith('.css'))
return issues;
const lines = content.split('\n');
const appleSpacingValues = ['4px', '8px', '12px', '16px', '20px', '24px', '32px', '40px', '48px', '64px'];
lines.forEach((line, index) => {
// Check for margin and padding values
const spacingRegex = /(margin|padding)(-[a-z]+)?:\s*([^;]+);/g;
let match;
while ((match = spacingRegex.exec(line)) !== null) {
const value = match[3].trim();
// Skip variables and calculations
if (value.includes('var(') || value.includes('calc(')) {
continue;
}
// Check if value uses Apple spacing scale
const hasValidSpacing = appleSpacingValues.some(validValue => value.includes(validValue) || value === '0');
if (!hasValidSpacing && /\d+px/.test(value)) {
issues.push({
rule: 'apple-spacing',
message: `Use Apple design system spacing values: ${appleSpacingValues.join(', ')}`,
severity: 'warn',
line: index + 1,
fixable: true,
category: 'apple-design'
});
}
}
});
return issues;
},
fix: (content, issues) => {
let fixedContent = content;
// Replace common non-standard spacing with Apple values
const spacingMap = {
'5px': '4px',
'6px': '8px',
'10px': '8px',
'14px': '12px',
'15px': '16px',
'18px': '16px',
'22px': '20px',
'25px': '24px',
'30px': '32px',
'35px': '32px',
'45px': '48px',
'50px': '48px',
'60px': '64px'
};
Object.entries(spacingMap).forEach(([oldValue, newValue]) => {
fixedContent = fixedContent.replace(new RegExp(`(margin|padding)([^:]*):([^;]*?)${oldValue}`, 'g'), `$1$2:$3${newValue}`);
});
return fixedContent;
}
},
{
name: 'apple-typography',
description: 'Enforce Apple typography guidelines',
category: 'apple-design',
severity: 'warn',
fixable: false,
check: (content, filePath, config) => {
const issues = [];
if (!filePath.endsWith('.css'))
return issues;
const lines = content.split('\n');
const appleFontSizes = ['11px', '12px', '13px', '14px', '16px', '18px', '20px', '24px', '28px', '32px', '40px', '48px'];
const appleFontWeights = ['300', '400', '500', '600', '700'];
lines.forEach((line, index) => {
// Check font-size
const fontSizeMatch = line.match(/font-size:\s*([^;]+);/);
if (fontSizeMatch) {
const fontSize = fontSizeMatch[1].trim();
if (!appleFontSizes.includes(fontSize) && /\d+px/.test(fontSize)) {
issues.push({
rule: 'apple-typography',
message: `Use Apple typography scale font sizes: ${appleFontSizes.join(', ')}`,
severity: 'warn',
line: index + 1,
fixable: false,
category: 'apple-design'
});
}
}
// Check font-weight
const fontWeightMatch = line.match(/font-weight:\s*([^;]+);/);
if (fontWeightMatch) {
const fontWeight = fontWeightMatch[1].trim();
if (!appleFontWeights.includes(fontWeight) && fontWeight !== 'normal' && fontWeight !== 'bold') {
issues.push({
rule: 'apple-typography',
message: `Use Apple typography font weights: ${appleFontWeights.join(', ')}, normal, bold`,
severity: 'warn',
line: index + 1,
fixable: false,
category: 'apple-design'
});
}
}
// Check for non-system fonts
if (line.includes('font-family') &&
!line.includes('-apple-system') &&
!line.includes('BlinkMacSystemFont') &&
!line.includes('San Francisco')) {
issues.push({
rule: 'apple-typography',
message: 'Consider using Apple system fonts: -apple-system, BlinkMacSystemFont, "SF Pro"',
severity: 'info',
line: index + 1,
fixable: false,
category: 'apple-design'
});
}
});
return issues;
}
},
{
name: 'apple-color-usage',
description: 'Encourage use of Apple design system colors',
category: 'apple-design',
severity: 'info',
fixable: false,
check: (content, filePath, config) => {
const issues = [];
if (!filePath.endsWith('.css'))
return issues;
const lines = content.split('\n');
const appleColors = {
blue: ['#007AFF', '#0051D5', '#34C759'],
red: ['#FF3B30', '#D70015'],
orange: ['#FF9500', '#FF6D00'],
yellow: ['#FFCC00', '#FFB800'],
green: ['#34C759', '#248A3D'],
purple: ['#AF52DE', '#7B68EE'],
gray: ['#8E8E93', '#C7C7CC', '#F2F2F7']
};
lines.forEach((line, index) => {
// Check for color values
const colorMatch = line.match(/(color|background-color|border-color):\s*(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3});/);
if (colorMatch) {
const colorValue = colorMatch[2].toUpperCase();
// Check if it's a standard Apple color
const isAppleColor = Object.values(appleColors).flat().some(appleColor => appleColor.toUpperCase() === colorValue);
if (!isAppleColor) {
issues.push({
rule: 'apple-color-usage',
message: 'Consider using Apple design system colors for consistency',
severity: 'info',
line: index + 1,
fixable: false,
category: 'apple-design'
});
}
}
});
return issues;
}
},
{
name: 'apple-layout-hierarchy',
description: 'Enforce Apple design layout hierarchy principles',
category: 'apple-design',
severity: 'warn',
fixable: false,
check: (content, filePath, config) => {
const issues = [];
if (!filePath.endsWith('.html'))
return issues;
const lines = content.split('\n');
lines.forEach((line, index) => {
// Check for proper heading hierarchy
const headingMatch = line.match(/<h([1-6])/);
if (headingMatch) {
const level = parseInt(headingMatch[1]);
// Look for previous headings to check hierarchy
const prevLines = lines.slice(0, index);
const lastHeadingMatch = prevLines.reverse().find(l => l.match(/<h[1-6]/));
if (lastHeadingMatch) {
const lastLevel = parseInt(lastHeadingMatch.match(/<h([1-6])/)?.[1] || '1');
if (level > lastLevel + 1) {
issues.push({
rule: 'apple-layout-hierarchy',
message: `Heading levels should not skip. Previous heading was h${lastLevel}, current is h${level}`,
severity: 'warn',
line: index + 1,
fixable: false,
category: 'apple-design'
});
}
}
}
// Check for proper button hierarchy
if (line.includes('<button') && line.includes('class=')) {
const hasDestructive = line.includes('destructive');
const hasPrimary = line.includes('primary');
// Count buttons in the same container
const containerButtons = content.split('\n').filter(l => l.includes('<button') && l.includes('primary')).length;
if (containerButtons > 1) {
issues.push({
rule: 'apple-layout-hierarchy',
message: 'Avoid multiple primary buttons in the same context. Use secondary buttons for additional actions',
severity: 'warn',
line: index + 1,
fixable: false,
category: 'apple-design'
});
}
}
});
return issues;
}
},
{
name: 'apple-responsive-design',
description: 'Ensure responsive design follows Apple guidelines',
category: 'apple-design',
severity: 'warn',
fixable: false,
check: (content, filePath, config) => {
const issues = [];
if (!filePath.endsWith('.css'))
return issues;
const lines = content.split('\n');
const appleBreakpoints = ['768px', '1024px', '1280px'];
lines.forEach((line, index) => {
// Check for media queries
if (line.includes('@media')) {
const breakpointMatch = line.match(/(\d+)px/);
if (breakpointMatch) {
const breakpoint = breakpointMatch[1] + 'px';
if (!appleBreakpoints.includes(breakpoint)) {
issues.push({
rule: 'apple-responsive-design',
message: `Consider using Apple standard breakpoints: ${appleBreakpoints.join(', ')}`,
severity: 'info',
line: index + 1,
fixable: false,
category: 'apple-design'
});
}
}
}
// Check for fixed widths that might break responsiveness
if (line.includes('width:') && line.includes('px') && !line.includes('min-width') && !line.includes('max-width')) {
const widthMatch = line.match(/width:\s*(\d+)px/);
if (widthMatch && parseInt(widthMatch[1]) > 320) {
issues.push({
rule: 'apple-responsive-design',
message: 'Consider using flexible units (%, vw, rem) instead of fixed pixel widths for responsive design',
severity: 'warn',
line: index + 1,
fixable: false,
category: 'apple-design'
});
}
}
});
return issues;
}
}
];
}
//# sourceMappingURL=apple-design.js.map