mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Sync generated provider output
This commit is contained in:
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
@@ -628,6 +628,36 @@ function colorToHex(c) {
|
||||
return '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// --- cli/engine/shared/fonts.mjs ---
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
// --- cli/engine/rules/checks.mjs ---
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2682,14 +2712,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
||||
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
||||
import { isNeutralColor } from '../../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
|
||||
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
@@ -38,6 +39,10 @@ function shouldRunPageAnalyzers(content, filePath) {
|
||||
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
||||
}
|
||||
|
||||
function firstOverusedGoogleFont(text) {
|
||||
return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
|
||||
}
|
||||
|
||||
function isNeutralBorderColor(str) {
|
||||
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
||||
if (!m) return false;
|
||||
@@ -88,9 +93,12 @@ const REGEX_MATCHERS = [
|
||||
{ id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => m[0] },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Instrument\+Serif|Mona\+Sans|Geist)\b/gi,
|
||||
test: () => true,
|
||||
fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` },
|
||||
{ id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
|
||||
test: (m) => {
|
||||
m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
|
||||
return Boolean(m.overusedGoogleFont);
|
||||
},
|
||||
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
||||
// --- Gradient text ---
|
||||
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
||||
test: (m, line) => /gradient/i.test(line),
|
||||
@@ -170,10 +178,7 @@ const REGEX_ANALYZERS = [
|
||||
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
||||
}
|
||||
}
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
while ((m = gfRe.exec(content)) !== null) {
|
||||
for (const f of m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase())) fonts.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(content)) fonts.add(f);
|
||||
if (fonts.size !== 1 || content.split('\n').length < 20) return [];
|
||||
const name = [...fonts][0];
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
parseRgb,
|
||||
relativeLuminance,
|
||||
} from '../shared/color.mjs';
|
||||
import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
|
||||
|
||||
const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
||||
|
||||
@@ -2072,14 +2073,9 @@ function checkPageTypography(doc, win) {
|
||||
|
||||
// Check Google Fonts links in HTML
|
||||
const html = doc.documentElement?.outerHTML || '';
|
||||
const gfRe = /fonts\.googleapis\.com\/css2?\?family=([^&"'\s]+)/gi;
|
||||
let m;
|
||||
while ((m = gfRe.exec(html)) !== null) {
|
||||
const families = m[1].split('|').map(f => f.split(':')[0].replace(/\+/g, ' ').toLowerCase());
|
||||
for (const f of families) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
for (const f of extractGoogleFontFamilies(html)) {
|
||||
fonts.add(f);
|
||||
if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
|
||||
}
|
||||
|
||||
// Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
||||
|
||||
function normalizeGoogleFontFamilyParam(value) {
|
||||
return String(value || '')
|
||||
.split('|')
|
||||
.map(part => part.split(':')[0].trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractGoogleFontFamilies(text) {
|
||||
const families = [];
|
||||
if (!text) return families;
|
||||
|
||||
GOOGLE_FONTS_URL_RE.lastIndex = 0;
|
||||
let urlMatch;
|
||||
while ((urlMatch = GOOGLE_FONTS_URL_RE.exec(text)) !== null) {
|
||||
const url = urlMatch[0];
|
||||
const queryStart = url.indexOf('?');
|
||||
if (queryStart === -1) continue;
|
||||
|
||||
const params = new URLSearchParams(url.slice(queryStart + 1).replace(/&/g, '&'));
|
||||
for (const value of params.getAll('family')) {
|
||||
families.push(...normalizeGoogleFontFamilyParam(value));
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
export { extractGoogleFontFamilies };
|
||||
Reference in New Issue
Block a user