Skip to content

Commit 894053a

Browse files
committed
feat: in case of undefined pseudos, show in which css level / module it is defined
1 parent bafafe9 commit 894053a

File tree

3 files changed

+62
-59
lines changed

3 files changed

+62
-59
lines changed

src/parser.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -775,11 +775,11 @@ export function createParser(
775775
// Generate a helpful error message with location information
776776
const locations = pseudoLocationIndex.pseudoElements[pseudoName];
777777
let errorMessage = `Unknown pseudo-element "${pseudoName}"`;
778-
778+
779779
if (locations && locations.length > 0) {
780780
errorMessage += `. It is defined in: ${locations.join(', ')}`;
781781
}
782-
782+
783783
fail(errorMessage + '.');
784784
}
785785

@@ -818,11 +818,11 @@ export function createParser(
818818
// Generate a helpful error message with location information
819819
const locations = pseudoLocationIndex.pseudoClasses[pseudoName];
820820
let errorMessage = `Unknown pseudo-class: "${pseudoName}"`;
821-
821+
822822
if (locations && locations.length > 0) {
823823
errorMessage += `. It is defined in: ${locations.join(', ')}`;
824824
}
825-
825+
826826
fail(errorMessage + '.');
827827
}
828828

src/syntax-definitions.ts

+48-45
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,35 @@ export interface PseudoLocationIndex {
510510
pseudoElements: Record<string, string[]>;
511511
}
512512

513+
const latestSyntaxDefinition = {
514+
...selectors4SyntaxDefinition,
515+
modules: (Object.entries(cssModules) as [CssModule, SyntaxDefinition & {latest?: boolean}][])
516+
.filter(([, {latest}]) => latest)
517+
.map(([name]) => name)
518+
};
519+
520+
const progressiveSyntaxDefinition = extendSyntaxDefinition(latestSyntaxDefinition, {
521+
pseudoElements: {
522+
unknown: 'accept'
523+
},
524+
pseudoClasses: {
525+
unknown: 'accept'
526+
},
527+
attributes: {
528+
unknownCaseSensitivityModifiers: 'accept'
529+
}
530+
});
531+
532+
export const cssSyntaxDefinitions: Record<CssLevel, SyntaxDefinition> = {
533+
css1: css1SyntaxDefinition,
534+
css2: css2SyntaxDefinition,
535+
css3: selectors3SyntaxDefinition,
536+
'selectors-3': selectors3SyntaxDefinition,
537+
'selectors-4': selectors4SyntaxDefinition,
538+
latest: latestSyntaxDefinition,
539+
progressive: progressiveSyntaxDefinition
540+
};
541+
513542
/**
514543
* Builds an index of where each pseudo-class and pseudo-element is defined
515544
* (in which CSS Level or CSS Module)
@@ -519,18 +548,18 @@ export function buildPseudoLocationIndex(): PseudoLocationIndex {
519548
pseudoClasses: {},
520549
pseudoElements: {}
521550
};
522-
551+
523552
// Add CSS Levels (excluding 'latest' and 'progressive')
524553
const cssLevels: CssLevel[] = ['css1', 'css2', 'css3', 'selectors-3', 'selectors-4'];
525-
554+
526555
for (const level of cssLevels) {
527556
const syntax = cssSyntaxDefinitions[level];
528-
557+
529558
// Process pseudo-classes
530559
if (syntax.pseudoClasses && typeof syntax.pseudoClasses === 'object') {
531-
const { definitions } = syntax.pseudoClasses;
560+
const {definitions} = syntax.pseudoClasses;
532561
if (definitions) {
533-
for (const [type, names] of Object.entries(definitions)) {
562+
for (const [, names] of Object.entries(definitions)) {
534563
for (const name of names) {
535564
if (!index.pseudoClasses[name]) {
536565
index.pseudoClasses[name] = [];
@@ -542,10 +571,10 @@ export function buildPseudoLocationIndex(): PseudoLocationIndex {
542571
}
543572
}
544573
}
545-
574+
546575
// Process pseudo-elements
547576
if (syntax.pseudoElements && typeof syntax.pseudoElements === 'object') {
548-
const { definitions } = syntax.pseudoElements;
577+
const {definitions} = syntax.pseudoElements;
549578
if (definitions) {
550579
if (Array.isArray(definitions)) {
551580
for (const name of definitions) {
@@ -557,7 +586,7 @@ export function buildPseudoLocationIndex(): PseudoLocationIndex {
557586
}
558587
}
559588
} else {
560-
for (const [type, names] of Object.entries(definitions)) {
589+
for (const names of Object.values(definitions)) {
561590
for (const name of names) {
562591
if (!index.pseudoElements[name]) {
563592
index.pseudoElements[name] = [];
@@ -571,14 +600,17 @@ export function buildPseudoLocationIndex(): PseudoLocationIndex {
571600
}
572601
}
573602
}
574-
603+
575604
// Add CSS Modules
576-
for (const [moduleName, moduleSyntax] of Object.entries(cssModules)) {
605+
for (const [moduleName, moduleSyntax] of Object.entries(cssModules) as [
606+
CssModule,
607+
SyntaxDefinition & {latest?: boolean}
608+
][]) {
577609
// Process pseudo-classes
578610
if (moduleSyntax.pseudoClasses && typeof moduleSyntax.pseudoClasses === 'object') {
579-
const { definitions } = moduleSyntax.pseudoClasses;
611+
const {definitions} = moduleSyntax.pseudoClasses;
580612
if (definitions) {
581-
for (const [type, names] of Object.entries(definitions)) {
613+
for (const names of Object.values(definitions)) {
582614
for (const name of names) {
583615
if (!index.pseudoClasses[name]) {
584616
index.pseudoClasses[name] = [];
@@ -590,10 +622,10 @@ export function buildPseudoLocationIndex(): PseudoLocationIndex {
590622
}
591623
}
592624
}
593-
625+
594626
// Process pseudo-elements
595627
if (moduleSyntax.pseudoElements && typeof moduleSyntax.pseudoElements === 'object') {
596-
const { definitions } = moduleSyntax.pseudoElements;
628+
const {definitions} = moduleSyntax.pseudoElements;
597629
if (definitions) {
598630
if (Array.isArray(definitions)) {
599631
for (const name of definitions) {
@@ -605,7 +637,7 @@ export function buildPseudoLocationIndex(): PseudoLocationIndex {
605637
}
606638
}
607639
} else {
608-
for (const [type, names] of Object.entries(definitions)) {
640+
for (const names of Object.values(definitions)) {
609641
for (const name of names) {
610642
if (!index.pseudoElements[name]) {
611643
index.pseudoElements[name] = [];
@@ -619,38 +651,9 @@ export function buildPseudoLocationIndex(): PseudoLocationIndex {
619651
}
620652
}
621653
}
622-
654+
623655
return index;
624656
}
625657

626658
// Pre-build the index for faster lookup
627659
export const pseudoLocationIndex = buildPseudoLocationIndex();
628-
629-
const latestSyntaxDefinition = {
630-
...selectors4SyntaxDefinition,
631-
modules: (Object.entries(cssModules) as [CssModule, SyntaxDefinition & {latest?: boolean}][])
632-
.filter(([, {latest}]) => latest)
633-
.map(([name]) => name)
634-
};
635-
636-
const progressiveSyntaxDefinition = extendSyntaxDefinition(latestSyntaxDefinition, {
637-
pseudoElements: {
638-
unknown: 'accept'
639-
},
640-
pseudoClasses: {
641-
unknown: 'accept'
642-
},
643-
attributes: {
644-
unknownCaseSensitivityModifiers: 'accept'
645-
}
646-
});
647-
648-
export const cssSyntaxDefinitions: Record<CssLevel, SyntaxDefinition> = {
649-
css1: css1SyntaxDefinition,
650-
css2: css2SyntaxDefinition,
651-
css3: selectors3SyntaxDefinition,
652-
'selectors-3': selectors3SyntaxDefinition,
653-
'selectors-4': selectors4SyntaxDefinition,
654-
latest: latestSyntaxDefinition,
655-
progressive: progressiveSyntaxDefinition
656-
};

test/modules.test.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ describe('CSS Modules', () => {
713713
})
714714
);
715715
});
716-
716+
717717
it('should provide helpful error messages with location information', () => {
718718
const parse = createParser({
719719
syntax: {
@@ -725,32 +725,32 @@ describe('CSS Modules', () => {
725725
}
726726
}
727727
});
728-
728+
729729
// Test for pseudo-class defined in a CSS module
730730
try {
731731
parse(':sticky');
732732
fail('Should have thrown an error');
733733
} catch (e) {
734-
expect(e.message).toContain('Unknown pseudo-class: "sticky"');
735-
expect(e.message).toContain('css-position-3');
734+
expect((e as Error).message).toContain('Unknown pseudo-class: "sticky"');
735+
expect((e as Error).message).toContain('css-position-3');
736736
}
737-
737+
738738
// Test for pseudo-element defined in a CSS module
739739
try {
740740
parse('::part(button)');
741741
fail('Should have thrown an error');
742742
} catch (e) {
743-
expect(e.message).toContain('Unknown pseudo-element "part"');
744-
expect(e.message).toContain('css-shadow-parts-1');
743+
expect((e as Error).message).toContain('Unknown pseudo-element "part"');
744+
expect((e as Error).message).toContain('css-shadow-parts-1');
745745
}
746-
746+
747747
// Test for pseudo-class defined in a CSS level
748748
try {
749749
parse(':focus-visible');
750750
fail('Should have thrown an error');
751751
} catch (e) {
752-
expect(e.message).toContain('Unknown pseudo-class: "focus-visible"');
753-
expect(e.message).toContain('selectors-4');
752+
expect((e as Error).message).toContain('Unknown pseudo-class: "focus-visible"');
753+
expect((e as Error).message).toContain('selectors-4');
754754
}
755755
});
756756
});

0 commit comments

Comments
 (0)