Sync generated provider output

This commit is contained in:
github-actions[bot]
2026-07-26 01:39:58 +00:00
parent 63ecc37e54
commit 5d77ba75fe
45 changed files with 600 additions and 60 deletions
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -1625,7 +1625,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;
@@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH }
import { isNeutralColor } from '../../shared/color.mjs';
import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
import { checkSourceDesignSystem } from '../../design-system.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
@@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) {
profile,
phase: 'source',
}));
if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath));
// Pseudo-element stripes (::before/::after absolute bars) carry the same
// side-tab silhouette without any border token, so the line matchers can't
// see them (issue #394). The shared scanner already runs on full HTML pages
// via checkHtmlPatterns; give standalone stylesheets, component style
// blocks, and CSS-in-JS templates the same coverage. Each hit carries the
// rule's source offset, so the finding gets a real line and line-scoped
// inline ignores keep working.
const pseudoStripeFindings = (text, lineOffset) =>
scanCssTextForPseudoStripe(text).map(hit =>
finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
if (cssLike.has(ext)) {
findings.push(...scanInsetStripeCss(content, filePath));
findings.push(...pseudoStripeFindings(content, 0));
}
// Block-level CSS checks that need multiple declarations must run over the
// complete source, not line-by-line. This covers standalone stylesheets,
@@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) {
// reported every selector one line low. runRegexMatchers keeps startLine - 1
// because it indexes its split lines from zero.
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
}
// Extract and scan CSS-in-JS template literals
@@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) {
phase: 'css-in-js',
}));
findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1));
findings.push(...pseudoStripeFindings(block.content, block.startLine - 1));
}
if (options?.designSystem) {
@@ -823,7 +823,13 @@ function isZeroOffset(value) {
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
// so this scans stylesheet text directly, mirroring the border rule's
// gates: >= 3px thick, chromatic fill, full height against a side edge.
function scanCssTextForPseudoStripe(content) {
function scanCssTextForPseudoStripe(rawContent) {
// Blank comment bodies byte-for-byte so commented-out rules are not
// scanned as live CSS and every rule keeps its source offset (each
// finding carries `index` so line-based callers can attribute it and
// line-scoped inline ignores can match).
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
(block) => block.replace(/[^\n]/g, ' '));
const customProps = collectCssCustomProps(content);
const findings = [];
const seen = new Set();
@@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) {
if (seen.has(selector)) continue;
seen.add(selector);
// The selector group absorbs whitespace trailing the previous rule;
// advance past it so `index` points at the selector itself.
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
findings.push({
id: 'side-tab',
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
index: selectorStart,
});
}
return findings;