From c3dbe967c9ae7268745762362c50daabde5885bb Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Mon, 31 Aug 2026 17:22:07 +0200 Subject: [PATCH] css-syntax-patches-for-csstree: add at-rule preludes --- .../CHANGELOG.md | 4 + .../dist/index.json | 83 ++++-- .../patches/webref-over-csstree-atrules.json | 265 ++++++++++++++++++ .../raw-data/csstree-atrules.json | 73 +++++ .../raw-data/webref-atrules.json | 117 ++++++++ .../raw-data/webref-over-csstree-atrules.json | 120 ++++++++ .../scripts/apply-patches.mjs | 130 ++++++--- .../scripts/diff-sets.mjs | 107 ++++--- .../scripts/generate-set.mjs | 16 +- .../scripts/generate-webref-sets.mjs | 95 +++++-- .../scripts/index.mjs | 22 +- .../scripts/write-patches.mjs | 68 +++-- .../tests/unit.test.mjs | 58 +++- 13 files changed, 1001 insertions(+), 157 deletions(-) diff --git a/packages/css-syntax-patches-for-csstree/CHANGELOG.md b/packages/css-syntax-patches-for-csstree/CHANGELOG.md index 1775173059..55711e01cb 100644 --- a/packages/css-syntax-patches-for-csstree/CHANGELOG.md +++ b/packages/css-syntax-patches-for-csstree/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to CSS Syntax Patches For CSSTree +### Unreleased (patch) + +- Add at-rule preludes + ### 1.1.10 _August 30, 2026_ diff --git a/packages/css-syntax-patches-for-csstree/dist/index.json b/packages/css-syntax-patches-for-csstree/dist/index.json index 808ed25d78..6a8ad3f03f 100644 --- a/packages/css-syntax-patches-for-csstree/dist/index.json +++ b/packages/css-syntax-patches-for-csstree/dist/index.json @@ -1,13 +1,73 @@ { "next": { "atrules": { + "apply": { + "prelude": "[ | ]" + }, "color-profile": { + "prelude": "[ | device-cmyk ]", "descriptors": { "components": "#", "rendering-intent": "relative-colorimetric | absolute-colorimetric | perceptual | saturation", "src": "" } }, + "container": { + "prelude": "#" + }, + "custom-media": { + "prelude": " [ | true | false ]" + }, + "custom-selector": { + "prelude": " " + }, + "else": { + "prelude": "?" + }, + "font-feature-values": { + "prelude": "#", + "descriptors": { + "font-display": "auto | block | swap | fallback | optional" + } + }, + "function": { + "prelude": " #? ) [ returns ]?", + "descriptors": { + "result": "?" + } + }, + "import": { + "prelude": "[ | ] [ [ layer | layer( ) ] || [ scope | scope( | ) ] || ]? " + }, + "mixin": { + "prelude": "[ #? ) | ]" + }, + "navigation": { + "prelude": "" + }, + "page": { + "prelude": "?", + "descriptors": { + "margin": "<'margin'>", + "margin-bottom": "<'margin-bottom'>", + "margin-left": "<'margin-left'>", + "margin-right": "<'margin-right'>", + "margin-top": "<'margin-top'>", + "page-margin-safety": "none | clamp | add" + } + }, + "property": { + "prelude": "#" + }, + "scope": { + "prelude": "?" + }, + "supports-condition": { + "prelude": "" + }, + "when": { + "prelude": "" + }, "font-face": { "descriptors": { "ascent-override": "[ normal | ]{1,2}", @@ -29,31 +89,11 @@ "unicode-range": "#" } }, - "font-feature-values": { - "descriptors": { - "font-display": "auto | block | swap | fallback | optional" - } - }, "font-palette-values": { "descriptors": { "font-family": "#" } }, - "function": { - "descriptors": { - "result": "?" - } - }, - "page": { - "descriptors": { - "margin": "<'margin'>", - "margin-bottom": "<'margin-bottom'>", - "margin-left": "<'margin-left'>", - "margin-right": "<'margin-right'>", - "margin-top": "<'margin-top'>", - "page-margin-safety": "none | clamp | add" - } - }, "position-try": { "descriptors": { "position-anchor": "<'position-anchor'>", @@ -480,6 +520,9 @@ }, "types": { "dashed-ident": "", + "extension-name": "", + "custom-selector": "", + "dashed-function": " ? )", "unicode-range-token": "", "a-n-plus-b": "odd | even | | | '+'? n | -n | | '+'? | | | '+'? n | -n | | '+'? n- | -n- | [ '+' | '-' ] | '+'? n [ '+' | '-' ] | -n [ '+' | '-' ] ", "alpha()": "alpha( [ from ] [ / [ | alpha | none ] ]? )", diff --git a/packages/css-syntax-patches-for-csstree/patches/webref-over-csstree-atrules.json b/packages/css-syntax-patches-for-csstree/patches/webref-over-csstree-atrules.json index 2acfa03894..8fe37e575c 100644 --- a/packages/css-syntax-patches-for-csstree/patches/webref-over-csstree-atrules.json +++ b/packages/css-syntax-patches-for-csstree/patches/webref-over-csstree-atrules.json @@ -1,4 +1,34 @@ { + "apply": { + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": "[ | ]", + "syntax-m": "[ | ]", + "tests": { + "passing": [ + { + "prelude": "--foo" + }, + { + "prelude": "--foo()" + }, + { + "prelude": "--foo(10px)" + }, + { + "prelude": "--foo(10px, 5rem)" + } + ], + "failing": [ + { + "prelude": "foo(10px, 5rem" + } + ] + } + } + }, "color-profile": { "descriptors": { "components": { @@ -48,6 +78,91 @@ "failing": [] } } + }, + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": "[ | device-cmyk ]", + "syntax-m": "[ | device-cmyk ]", + "tests": { + "passing": [], + "failing": [] + } + } + }, + "container": { + "prelude": { + "type": "modified", + "comment": "", + "syntax-b": "[ ]? ", + "syntax-a": "#", + "syntax-m": "#", + "tests": { + "passing": [], + "failing": [] + } + } + }, + "custom-media": { + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": " [ | true | false ]", + "syntax-m": " [ | true | false ]", + "tests": { + "passing": [ + { + "prelude": "--foo screen" + }, + { + "prelude": "--foo (min-width: 320px)" + }, + { + "prelude": "--foo (width >= 35rem) or (not (prefers-color-scheme: dark))" + } + ], + "failing": [ + { + "prelude": "foo screen" + } + ] + } + } + }, + "custom-selector": { + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": " ", + "syntax-m": " ", + "tests": { + "passing": [ + { + "prelude": "--button button, [type=button]" + } + ], + "failing": [ + { + "prelude": "foo button, [type=button]" + } + ] + } + } + }, + "else": { + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": "?", + "syntax-m": "?", + "tests": { + "passing": [], + "failing": [] + } } }, "font-face": { @@ -285,6 +400,17 @@ "failing": [] } } + }, + "prelude": { + "type": "modified", + "comment": "", + "syntax-b": "#", + "syntax-a": "#", + "syntax-m": "#", + "tests": { + "passing": [], + "failing": [] + } } }, "font-palette-values": { @@ -315,6 +441,60 @@ "failing": [] } } + }, + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": " #? ) [ returns ]?", + "syntax-m": " #? ) [ returns ]?", + "tests": { + "passing": [], + "failing": [] + } + } + }, + "import": { + "prelude": { + "type": "modified", + "comment": "", + "syntax-b": "[ | ] [ layer | layer( ) ]? [ supports( [ | ] ) ]? ?", + "syntax-a": "[ | ] [ [ layer | layer( ) ] || [ scope | scope( | ) ] || ]? ", + "syntax-m": "[ | ] [ [ layer | layer( ) ] || [ scope | scope( | ) ] || ]? ", + "tests": { + "passing": [], + "failing": [] + } + } + }, + "mixin": { + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": "[ #? ) | ]", + "syntax-m": "[ #? ) | ]", + "tests": { + "passing": [ + { + "prelude": "--foo(--bar: 2rem)" + } + ], + "failing": [] + } + } + }, + "navigation": { + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": "", + "syntax-m": "", + "tests": { + "passing": [], + "failing": [] + } } }, "page": { @@ -385,6 +565,17 @@ "failing": [] } } + }, + "prelude": { + "type": "modified", + "comment": "", + "syntax-b": "", + "syntax-a": "?", + "syntax-m": "?", + "tests": { + "passing": [], + "failing": [] + } } }, "position-try": { @@ -413,6 +604,67 @@ } } }, + "property": { + "prelude": { + "type": "modified", + "comment": "", + "syntax-b": "", + "syntax-a": "#", + "syntax-m": "#", + "tests": { + "passing": [ + { + "prelude": "--foo" + }, + { + "prelude": "--foo, --bar" + } + ], + "failing": [ + { + "prelude": "foo" + } + ] + } + } + }, + "scope": { + "prelude": { + "type": "modified", + "comment": "", + "syntax-b": "[ ( ) ]? [ to ( ) ]?", + "syntax-a": "?", + "syntax-m": "?", + "tests": { + "passing": [ + { + "prelude": "(.foo)" + }, + { + "prelude": "(.foo) to (.bar)" + } + ], + "failing": [ + { + "prelude": ".foo" + } + ] + } + } + }, + "supports-condition": { + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": "", + "syntax-m": "", + "tests": { + "passing": [], + "failing": [] + } + } + }, "view-transition": { "descriptors": { "types": { @@ -427,5 +679,18 @@ } } } + }, + "when": { + "prelude": { + "type": "added", + "comment": "", + "syntax-b": "", + "syntax-a": "", + "syntax-m": "", + "tests": { + "passing": [], + "failing": [] + } + } } } diff --git a/packages/css-syntax-patches-for-csstree/raw-data/csstree-atrules.json b/packages/css-syntax-patches-for-csstree/raw-data/csstree-atrules.json index f9919a5af0..2e4412c29f 100644 --- a/packages/css-syntax-patches-for-csstree/raw-data/csstree-atrules.json +++ b/packages/css-syntax-patches-for-csstree/raw-data/csstree-atrules.json @@ -1,5 +1,18 @@ { + "charset": { + "prelude": { + "syntax": "" + } + }, + "container": { + "prelude": { + "syntax": "[ ]? " + } + }, "counter-style": { + "prelude": { + "syntax": "" + }, "descriptors": { "additive-symbols": { "syntax": "[ && ]#" @@ -33,6 +46,11 @@ } } }, + "document": { + "prelude": { + "syntax": "[ | url-prefix( ) | domain( ) | media-document( ) | regexp( ) ]#" + } + }, "font-face": { "descriptors": { "ascent-override": { @@ -76,7 +94,15 @@ } } }, + "font-feature-values": { + "prelude": { + "syntax": "#" + } + }, "font-features-values": { + "prelude": { + "syntax": "[ | ]+" + }, "descriptors": { "font-display": { "syntax": "auto | block | swap | fallback | optional" @@ -84,6 +110,9 @@ } }, "font-palette-values": { + "prelude": { + "syntax": "" + }, "descriptors": { "base-palette": { "syntax": "light | dark | " @@ -96,7 +125,35 @@ } } }, + "import": { + "prelude": { + "syntax": "[ | ] [ layer | layer( ) ]? [ supports( [ | ] ) ]? ?" + } + }, + "keyframes": { + "prelude": { + "syntax": "" + } + }, + "layer": { + "prelude": { + "syntax": "[ # | ? ]" + } + }, + "media": { + "prelude": { + "syntax": "" + } + }, + "namespace": { + "prelude": { + "syntax": "? [ | ]" + } + }, "page": { + "prelude": { + "syntax": "" + }, "descriptors": { "bleed": { "syntax": "auto | " @@ -113,6 +170,9 @@ } }, "position-try": { + "prelude": { + "syntax": "" + }, "descriptors": { "align-self": { "syntax": "<'align-self'> | anchor-center" @@ -225,6 +285,9 @@ } }, "property": { + "prelude": { + "syntax": "" + }, "descriptors": { "inherits": { "syntax": "true | false" @@ -237,6 +300,16 @@ } } }, + "scope": { + "prelude": { + "syntax": "[ ( ) ]? [ to ( ) ]?" + } + }, + "supports": { + "prelude": { + "syntax": "" + } + }, "view-transition": { "descriptors": { "navigation": { diff --git a/packages/css-syntax-patches-for-csstree/raw-data/webref-atrules.json b/packages/css-syntax-patches-for-csstree/raw-data/webref-atrules.json index 8d7b262c2c..57a74e3787 100644 --- a/packages/css-syntax-patches-for-csstree/raw-data/webref-atrules.json +++ b/packages/css-syntax-patches-for-csstree/raw-data/webref-atrules.json @@ -1,5 +1,18 @@ { + "apply": { + "prelude": { + "syntax": "[ | ]" + } + }, + "charset": { + "prelude": { + "syntax": "" + } + }, "color-profile": { + "prelude": { + "syntax": "[ | device-cmyk ]" + }, "descriptors": { "components": { "syntax": "#" @@ -12,7 +25,15 @@ } } }, + "container": { + "prelude": { + "syntax": "#" + } + }, "counter-style": { + "prelude": { + "syntax": "" + }, "descriptors": { "additive-symbols": { "syntax": "[ && ]#" @@ -46,6 +67,26 @@ } } }, + "custom-media": { + "prelude": { + "syntax": " [ | true | false ]" + } + }, + "custom-selector": { + "prelude": { + "syntax": " " + } + }, + "document": { + "prelude": { + "syntax": "[ | url-prefix( ) | domain( ) | media-document( ) | regexp( ) ]#" + } + }, + "else": { + "prelude": { + "syntax": "?" + } + }, "font-face": { "descriptors": { "ascent-override": { @@ -114,6 +155,9 @@ } }, "font-feature-values": { + "prelude": { + "syntax": "#" + }, "descriptors": { "font-display": { "syntax": "auto | block | swap | fallback | optional" @@ -121,6 +165,9 @@ } }, "font-features-values": { + "prelude": { + "syntax": "[ | ]+" + }, "descriptors": { "font-display": { "syntax": "auto | block | swap | fallback | optional" @@ -128,6 +175,9 @@ } }, "font-palette-values": { + "prelude": { + "syntax": "" + }, "descriptors": { "base-palette": { "syntax": "light | dark | " @@ -141,13 +191,54 @@ } }, "function": { + "prelude": { + "syntax": " #? ) [ returns ]?" + }, "descriptors": { "result": { "syntax": "?" } } }, + "import": { + "prelude": { + "syntax": "[ | ] [ [ layer | layer( ) ] || [ scope | scope( | ) ] || ]? " + } + }, + "keyframes": { + "prelude": { + "syntax": "" + } + }, + "layer": { + "prelude": { + "syntax": "[ # | ? ]" + } + }, + "media": { + "prelude": { + "syntax": "" + } + }, + "mixin": { + "prelude": { + "syntax": "[ #? ) | ]" + } + }, + "namespace": { + "prelude": { + "syntax": "? [ | ]" + } + }, + "navigation": { + "prelude": { + "syntax": "" + } + }, "page": { + "prelude": { + "syntax": "?" + }, "descriptors": { "bleed": { "syntax": "auto | " @@ -182,6 +273,9 @@ } }, "position-try": { + "prelude": { + "syntax": "" + }, "descriptors": { "align-self": { "syntax": "<'align-self'> | anchor-center" @@ -300,6 +394,9 @@ } }, "property": { + "prelude": { + "syntax": "#" + }, "descriptors": { "inherits": { "syntax": "true | false" @@ -312,6 +409,21 @@ } } }, + "scope": { + "prelude": { + "syntax": "?" + } + }, + "supports": { + "prelude": { + "syntax": "" + } + }, + "supports-condition": { + "prelude": { + "syntax": "" + } + }, "view-transition": { "descriptors": { "navigation": { @@ -321,5 +433,10 @@ "syntax": "none | +" } } + }, + "when": { + "prelude": { + "syntax": "" + } } } diff --git a/packages/css-syntax-patches-for-csstree/raw-data/webref-over-csstree-atrules.json b/packages/css-syntax-patches-for-csstree/raw-data/webref-over-csstree-atrules.json index c7c802eb4d..1e400c6020 100644 --- a/packages/css-syntax-patches-for-csstree/raw-data/webref-over-csstree-atrules.json +++ b/packages/css-syntax-patches-for-csstree/raw-data/webref-over-csstree-atrules.json @@ -1,5 +1,19 @@ { + "apply": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": "[ | ]", + "comment": "" + } + }, "color-profile": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": "[ | device-cmyk ]", + "comment": "" + }, "descriptors": { "components": { "type": "added", @@ -21,6 +35,38 @@ } } }, + "container": { + "prelude": { + "type": "modified", + "syntax-b": "[ ]? ", + "syntax-a": "#", + "comment": "" + } + }, + "custom-media": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": " [ | true | false ]", + "comment": "" + } + }, + "custom-selector": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": " ", + "comment": "" + } + }, + "else": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": "?", + "comment": "" + } + }, "font-face": { "descriptors": { "ascent-override": { @@ -128,6 +174,12 @@ } }, "font-feature-values": { + "prelude": { + "type": "modified", + "syntax-b": "#", + "syntax-a": "#", + "comment": "" + }, "descriptors": { "font-display": { "type": "added", @@ -148,6 +200,12 @@ } }, "function": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": " #? ) [ returns ]?", + "comment": "" + }, "descriptors": { "result": { "type": "added", @@ -157,7 +215,37 @@ } } }, + "import": { + "prelude": { + "type": "modified", + "syntax-b": "[ | ] [ layer | layer( ) ]? [ supports( [ | ] ) ]? ?", + "syntax-a": "[ | ] [ [ layer | layer( ) ] || [ scope | scope( | ) ] || ]? ", + "comment": "" + } + }, + "mixin": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": "[ #? ) | ]", + "comment": "" + } + }, + "navigation": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": "", + "comment": "" + } + }, "page": { + "prelude": { + "type": "modified", + "syntax-b": "", + "syntax-a": "?", + "comment": "" + }, "descriptors": { "margin": { "type": "added", @@ -213,6 +301,30 @@ } } }, + "property": { + "prelude": { + "type": "modified", + "syntax-b": "", + "syntax-a": "#", + "comment": "" + } + }, + "scope": { + "prelude": { + "type": "modified", + "syntax-b": "[ ( ) ]? [ to ( ) ]?", + "syntax-a": "?", + "comment": "" + } + }, + "supports-condition": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": "", + "comment": "" + } + }, "view-transition": { "descriptors": { "types": { @@ -222,5 +334,13 @@ "comment": "" } } + }, + "when": { + "prelude": { + "type": "added", + "syntax-b": "", + "syntax-a": "", + "comment": "" + } } } diff --git a/packages/css-syntax-patches-for-csstree/scripts/apply-patches.mjs b/packages/css-syntax-patches-for-csstree/scripts/apply-patches.mjs index ad169a6724..767fb68e1f 100644 --- a/packages/css-syntax-patches-for-csstree/scripts/apply-patches.mjs +++ b/packages/css-syntax-patches-for-csstree/scripts/apply-patches.mjs @@ -8,52 +8,103 @@ export function apply_patches(patches, onto) { let atrules = Object.create(null); for (const [name, definition] of Object.entries(onto.atrules)) { - for (const [descriptor_name, descriptor] of Object.entries(definition.descriptors)) { - const patch = patches.atrules[name]?.descriptors[descriptor_name]; - if (!patch) { - // eslint-disable-next-line no-console - console.log(`Missing patch for descriptor '${descriptor_name}' for '@${name}'`); - has_missing_patches = true; - flaws++; - - continue; - } + const prelude = definition.prelude; + if (!prelude) { + // unsure if we can continue if there is no prelude here + continue; + } - if ( - patch['syntax-b'] !== descriptor['syntax-b'] || - patch['syntax-a'] !== descriptor['syntax-a'] - ) { - // eslint-disable-next-line no-console - console.log(`Outdated patch for descriptor '${descriptor_name}' for '@${name}'`); - has_outdated_patches = true; - flaws++; + const patch = patches.atrules[name]?.prelude; + if (!patch) { + // eslint-disable-next-line no-console + console.log(`Missing patch for prelude for '@${name}'`); + has_missing_patches = true; + flaws++; - continue; - } + continue; + } - if (patch.omit) { - continue; - } + if ( + patch['syntax-b'] !== prelude['syntax-b'] || + patch['syntax-a'] !== prelude['syntax-a'] + ) { + // eslint-disable-next-line no-console + console.log(`Outdated patch for prelude for '@${name}'`); + has_outdated_patches = true; + flaws++; - if (!patch['syntax-m']) { - // eslint-disable-next-line no-console - console.log(`Unmerged patch for descriptor '${descriptor_name}' for '@${name}'`); - has_unmerged_patches = true; - flaws++; + continue; + } - continue; - } + if (patch.omit) { + continue; + } - if (patch['syntax-m'] === patch['syntax-b']) { - // CSSTree is most correct - continue; - } + if (!patch['syntax-m']) { + // eslint-disable-next-line no-console + console.log(`Unmerged patch for prelude for '@${name}'`); + has_unmerged_patches = true; + flaws++; - atrules[name] ??= { - descriptors: Object(), - }; + continue; + } - atrules[name].descriptors[descriptor_name] = patch['syntax-m']; + if (patch['syntax-m'] === patch['syntax-b']) { + // CSSTree is most correct + continue; + } + + atrules[name] ??= Object.create(null); + atrules[name].prelude = patch['syntax-m']; + } + + for (const [name, definition] of Object.entries(onto.atrules)) { + if (definition.descriptors) { + for (const [descriptor_name, descriptor] of Object.entries(definition.descriptors)) { + const patch = patches.atrules[name]?.descriptors[descriptor_name]; + if (!patch) { + // eslint-disable-next-line no-console + console.log(`Missing patch for descriptor '${descriptor_name}' for '@${name}'`); + has_missing_patches = true; + flaws++; + + continue; + } + + if ( + patch['syntax-b'] !== descriptor['syntax-b'] || + patch['syntax-a'] !== descriptor['syntax-a'] + ) { + // eslint-disable-next-line no-console + console.log(`Outdated patch for descriptor '${descriptor_name}' for '@${name}'`); + has_outdated_patches = true; + flaws++; + + continue; + } + + if (patch.omit) { + continue; + } + + if (!patch['syntax-m']) { + // eslint-disable-next-line no-console + console.log(`Unmerged patch for descriptor '${descriptor_name}' for '@${name}'`); + has_unmerged_patches = true; + flaws++; + + continue; + } + + if (patch['syntax-m'] === patch['syntax-b']) { + // CSSTree is most correct + continue; + } + + atrules[name] ??= Object.create(null); + atrules[name].descriptors ??= Object.create(null); + atrules[name].descriptors[descriptor_name] = patch['syntax-m']; + } } } @@ -107,6 +158,9 @@ export function apply_patches(patches, onto) { // Manual patches to smooth over compat between csstree and webref/css types['dashed-ident'] = ''; + types['extension-name'] = ''; + types['custom-selector'] = ''; + types['dashed-function'] = ' ? )'; types['unicode-range-token'] = ''; for (const [name, definition] of Object.entries(onto.types)) { diff --git a/packages/css-syntax-patches-for-csstree/scripts/diff-sets.mjs b/packages/css-syntax-patches-for-csstree/scripts/diff-sets.mjs index 04a42b9e3f..915269bd08 100644 --- a/packages/css-syntax-patches-for-csstree/scripts/diff-sets.mjs +++ b/packages/css-syntax-patches-for-csstree/scripts/diff-sets.mjs @@ -46,63 +46,90 @@ export function diff_atrule_sets(a, b) { const diff = Object.create(null); for (const [name, definition] of Object.entries(a)) { - if (!definition.descriptors) { - continue; - } - - for (const [descriptor_name, descriptor_definition] of Object.entries(definition.descriptors)) { - if (!b[name]?.descriptors?.[descriptor_name]) { - diff[name] ??= { - descriptors: {}, - }; + if (definition.prelude?.syntax && !b[name]?.prelude?.syntax) { + diff[name] ??= Object.create(null); - // Note: Currently impossible for items to be removed because we fork the existing syntax and add to it. - diff[name].descriptors[descriptor_name] = { - type: 'deleted', - 'syntax-b': descriptor_definition.syntax, - 'syntax-a': '', - 'comment': '', - }; + // Note: Currently impossible for items to be removed because we fork the existing syntax and add to it. + diff[name].prelude = { + type: 'deleted', + 'syntax-b': definition.prelude.syntax, + 'syntax-a': '', + 'comment': '', + }; + } - continue; + if (definition.descriptors) { + for (const [descriptor_name, descriptor_definition] of Object.entries(definition.descriptors)) { + if (!b[name]?.descriptors?.[descriptor_name]) { + diff[name] ??= Object.create(null); + diff[name].descriptors ??= Object.create(null); + + // Note: Currently impossible for items to be removed because we fork the existing syntax and add to it. + diff[name].descriptors[descriptor_name] = { + type: 'deleted', + 'syntax-b': descriptor_definition.syntax, + 'syntax-a': '', + 'comment': '', + }; + + continue; + } } } } for (const [name, definition] of Object.entries(b)) { - if (!definition.descriptors) { - continue; - } + if (definition.prelude?.syntax) { + if (!a[name]?.prelude?.syntax) { + diff[name] ??= Object.create(null); - for (const [descriptor_name, descriptor_definition] of Object.entries(definition.descriptors)) { - if (!a[name]?.descriptors?.[descriptor_name]) { - diff[name] ??= { - descriptors: {}, - }; - - diff[name].descriptors[descriptor_name] = { + diff[name].prelude = { type: 'added', 'syntax-b': '', - 'syntax-a': descriptor_definition.syntax, + 'syntax-a': definition.prelude.syntax, 'comment': '', }; + } else if (a[name].prelude.syntax !== definition.prelude.syntax) { + diff[name] ??= Object.create(null); - continue; - } - - if (a[name]?.descriptors?.[descriptor_name].syntax !== b[name]?.descriptors?.[descriptor_name].syntax) { - diff[name] ??= { - descriptors: {}, - }; - - diff[name].descriptors[descriptor_name] = { + diff[name].prelude = { type: 'modified', - 'syntax-b': a[name].descriptors?.[descriptor_name].syntax, - 'syntax-a': b[name].descriptors?.[descriptor_name].syntax, + 'syntax-b': a[name].prelude.syntax, + 'syntax-a': definition.prelude.syntax, 'comment': '', }; + } + } - continue; + if (definition.descriptors) { + for (const [descriptor_name, descriptor_definition] of Object.entries(definition.descriptors)) { + if (!a[name]?.descriptors?.[descriptor_name]) { + diff[name] ??= Object.create(null); + diff[name].descriptors ??= Object.create(null); + + diff[name].descriptors[descriptor_name] = { + type: 'added', + 'syntax-b': '', + 'syntax-a': descriptor_definition.syntax, + 'comment': '', + }; + + continue; + } + + if (a[name]?.descriptors?.[descriptor_name].syntax !== b[name]?.descriptors?.[descriptor_name].syntax) { + diff[name] ??= Object.create(null); + diff[name].descriptors ??= Object.create(null); + + diff[name].descriptors[descriptor_name] = { + type: 'modified', + 'syntax-b': a[name].descriptors?.[descriptor_name].syntax, + 'syntax-a': b[name].descriptors?.[descriptor_name].syntax, + 'comment': '', + }; + + continue; + } } } } diff --git a/packages/css-syntax-patches-for-csstree/scripts/generate-set.mjs b/packages/css-syntax-patches-for-csstree/scripts/generate-set.mjs index 1993498f0e..abd23e3fca 100644 --- a/packages/css-syntax-patches-for-csstree/scripts/generate-set.mjs +++ b/packages/css-syntax-patches-for-csstree/scripts/generate-set.mjs @@ -21,13 +21,21 @@ export function generate_atrule_set(source) { const set = Object.create(null); for (const [name, definition] of Object.entries(source)) { - if (!definition.descriptors) { + if (!definition.prelude && !definition.descriptors) { continue; } - set[name] = { - descriptors: generate_set(definition.descriptors), - }; + if (definition.prelude) { + set[name] ??= {}; + set[name].prelude = { + syntax: definitionSyntax.generate(definition.prelude.syntax), + }; + } + + if (definition.descriptors) { + set[name] ??= {}; + set[name].descriptors = generate_set(definition.descriptors); + } } return sort_atrule_set(set); diff --git a/packages/css-syntax-patches-for-csstree/scripts/generate-webref-sets.mjs b/packages/css-syntax-patches-for-csstree/scripts/generate-webref-sets.mjs index b80ce64eab..a7843cb0c6 100644 --- a/packages/css-syntax-patches-for-csstree/scripts/generate-webref-sets.mjs +++ b/packages/css-syntax-patches-for-csstree/scripts/generate-webref-sets.mjs @@ -1,7 +1,7 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import css from '@webref/css'; -import { fork } from 'css-tree'; +import { definitionSyntax, fork } from 'css-tree'; import { generate_atrule_set, generate_set } from './generate-set.mjs'; import { trim_at } from './trim-at.mjs'; @@ -16,6 +16,38 @@ function assign_new_definition(set, name, definition) { set[name] = definition; } +function assign_new_atrule_syntax(set, atrule, syntax) { + const exists_in_set = set[atrule]?.prelude; + + if (exists_in_set) { + // eslint-disable-next-line no-console + console.error(`duplicate atrule prelude @${atrule} ${exists_in_set}`); + } + + set[atrule] ??= Object.create(null); + + let parsedSyntax = definitionSyntax.parse(syntax.trim().replaceAll('[ { } ]?', '')); + + parsedSyntax.terms = parsedSyntax.terms.slice(1); + + if (parsedSyntax.terms.some((x) => x.value === '{')) { + parsedSyntax.terms = parsedSyntax.terms.slice( + 0, + parsedSyntax.terms.findIndex((x) => x.value === '{'), + ); + } + + if (parsedSyntax.terms.at(-1)?.value === ';') { + parsedSyntax.terms = parsedSyntax.terms.slice(0, -1); + } + + if (!parsedSyntax.terms.length) { + return; + } + + set[atrule].prelude = definitionSyntax.generate(parsedSyntax); +} + function assign_new_atrule_descriptor_definition(set, atrule, name, definition) { const exists_in_set = set[atrule]?.descriptors?.[name]; @@ -24,9 +56,8 @@ function assign_new_atrule_descriptor_definition(set, atrule, name, definition) console.error(`duplicate atrule descriptor @${atrule} ${name}`); } - set[atrule] ??= { - descriptors: Object.create(null), - }; + set[atrule] ??= Object.create(null); + set[atrule].descriptors ??= Object.create(null); set[atrule].descriptors[name] = definition; } @@ -108,30 +139,44 @@ export async function generate_webref_sets() { const data = await css.listAll(); for (const atrule of data.atrules) { - if (!atrule.descriptors?.length) { - continue; - } - - for (const descriptor of atrule.descriptors) { - if (descriptor.name.startsWith('-webkit-')) { - continue; - } - - if (!descriptor.syntax) { - continue; + if (atrule.syntax) { + if (trim_at(atrule.name) === 'layer') { + assign_new_atrule_syntax( + atrules, + trim_at(atrule.name), + '?', + ); + } else { + assign_new_atrule_syntax( + atrules, + trim_at(atrule.name), + atrule.syntax, + ); } + } - if (descriptor.type === 'discrete' || descriptor.type === 'range') { - // definitions for features in `@container` and `@media` - continue; + if (atrule.descriptors?.length) { + for (const descriptor of atrule.descriptors) { + if (descriptor.name.startsWith('-webkit-')) { + continue; + } + + if (!descriptor.syntax) { + continue; + } + + if (descriptor.type === 'discrete' || descriptor.type === 'range') { + // definitions for features in `@container` and `@media` + continue; + } + + assign_new_atrule_descriptor_definition( + atrules, + trim_at(atrule.name), + descriptor.name, + descriptor.syntax, + ); } - - assign_new_atrule_descriptor_definition( - atrules, - trim_at(atrule.name), - descriptor.name, - descriptor.syntax, - ); } } diff --git a/packages/css-syntax-patches-for-csstree/scripts/index.mjs b/packages/css-syntax-patches-for-csstree/scripts/index.mjs index 15b3d2650d..362b32534f 100644 --- a/packages/css-syntax-patches-for-csstree/scripts/index.mjs +++ b/packages/css-syntax-patches-for-csstree/scripts/index.mjs @@ -39,8 +39,8 @@ flaws += patch_flaws; // Atrules for (const [name, atrule] of Object.entries(webref_over_csstree_sets.atrules)) { - for (const [descriptor_name] of Object.entries(atrule.descriptors)) { - const patch = patches.webref_over_csstree.atrules[name]?.descriptors?.[descriptor_name]; + if (atrule.prelude) { + const patch = patches.webref_over_csstree.atrules[name]?.prelude; if (!patch) { continue; } @@ -54,6 +54,24 @@ for (const [name, atrule] of Object.entries(webref_over_csstree_sets.atrules)) { continue; } } + + if (atrule.descriptors) { + for (const [descriptor_name] of Object.entries(atrule.descriptors)) { + const patch = patches.webref_over_csstree.atrules[name]?.descriptors?.[descriptor_name]; + if (!patch) { + continue; + } + + if (patch.omit) { + continue; + } + + if (!patch.tests) { + has_missing_patch_tests = true; + continue; + } + } + } } // Properties diff --git a/packages/css-syntax-patches-for-csstree/scripts/write-patches.mjs b/packages/css-syntax-patches-for-csstree/scripts/write-patches.mjs index 50fcf130a6..780c274fb5 100644 --- a/packages/css-syntax-patches-for-csstree/scripts/write-patches.mjs +++ b/packages/css-syntax-patches-for-csstree/scripts/write-patches.mjs @@ -65,18 +65,25 @@ export async function write_patches(sets, patch_sets) { continue; } - if (patch_definition.descriptors) { - merged_sets[set_name][kind_name][name] = { - descriptors: Object.create(null), - }; - - for (const [descriptor_name, patch_descriptor] of Object.entries(patch_definition.descriptors)) { - const descriptor = definition.descriptors[descriptor_name]; - if (!descriptor) { - continue; + if (patch_definition.descriptors || patch_definition.prelude) { + if (patch_definition.descriptors) { + merged_sets[set_name][kind_name][name] ??= Object.create(null); + merged_sets[set_name][kind_name][name].descriptors ??= Object.create(null); + + for (const [descriptor_name, patch_descriptor] of Object.entries(patch_definition.descriptors)) { + const descriptor = definition.descriptors[descriptor_name]; + if (!descriptor) { + continue; + } + + merged_sets[set_name][kind_name][name].descriptors[descriptor_name] = format_from_definition_to_patch(descriptor, patch_descriptor); } + } + + if (patch_definition.prelude) { + merged_sets[set_name][kind_name][name] ??= Object.create(null); - merged_sets[set_name][kind_name][name].descriptors[descriptor_name] = format_from_definition_to_patch(descriptor, patch_descriptor); + merged_sets[set_name][kind_name][name].prelude = format_from_definition_to_patch(definition.prelude, patch_definition.prelude); } continue; @@ -102,18 +109,29 @@ export async function write_patches(sets, patch_sets) { } for (const [name, definition] of Object.entries(kind)) { - if (definition.descriptors) { - merged_sets[set_name][kind_name][name] ??= { - descriptors: Object.create(null), - }; - - for (const [descriptor_name, descriptor] of Object.entries(definition.descriptors)) { - const patch_descriptor = patch_kind[name]?.descriptors?.[descriptor_name]; - if (patch_descriptor) { + if (definition.descriptors || definition.prelude) { + if (definition.descriptors) { + merged_sets[set_name][kind_name][name] ??= Object.create(null); + merged_sets[set_name][kind_name][name].descriptors ??= Object.create(null); + + for (const [descriptor_name, descriptor] of Object.entries(definition.descriptors)) { + const patch_descriptor = patch_kind[name]?.descriptors?.[descriptor_name]; + if (patch_descriptor) { + continue; + } + + merged_sets[set_name][kind_name][name].descriptors[descriptor_name] = format_definition(descriptor); + } + } + + if (definition.prelude) { + if (merged_sets[set_name][kind_name][name]?.prelude) { continue; } - merged_sets[set_name][kind_name][name].descriptors[descriptor_name] = format_definition(descriptor); + merged_sets[set_name][kind_name][name] ??= Object.create(null); + + merged_sets[set_name][kind_name][name].prelude = format_definition(definition.prelude); } continue; @@ -132,9 +150,15 @@ export async function write_patches(sets, patch_sets) { for (const [set_name, merged_set] of Object.entries(merged_sets)) { for (const [kind_name, merged_kind] of Object.entries(merged_set)) { for (const [name, merged_definition] of Object.entries(merged_kind)) { - if (merged_definition.descriptors) { - for (const [descriptor_name, descriptor] of Object.entries(merged_definition.descriptors)) { - merged_sets[set_name][kind_name][name].descriptors[descriptor_name] = sort_object_keys(descriptor); + if (merged_definition.descriptors || merged_definition.prelude) { + if (merged_definition.descriptors) { + for (const [descriptor_name, descriptor] of Object.entries(merged_definition.descriptors)) { + merged_sets[set_name][kind_name][name].descriptors[descriptor_name] = sort_object_keys(descriptor); + } + } + + if (merged_definition.prelude) { + merged_sets[set_name][kind_name][name].prelude = sort_object_keys(merged_definition.prelude); } } else { merged_sets[set_name][kind_name][name] = sort_object_keys(merged_definition); diff --git a/packages/css-syntax-patches-for-csstree/tests/unit.test.mjs b/packages/css-syntax-patches-for-csstree/tests/unit.test.mjs index d137c7e986..c33b2fb61e 100644 --- a/packages/css-syntax-patches-for-csstree/tests/unit.test.mjs +++ b/packages/css-syntax-patches-for-csstree/tests/unit.test.mjs @@ -43,8 +43,8 @@ if (invalid) { // Atrules for (const [name, atrule] of Object.entries(patches.atrules)) { - for (const [descriptor_name] of Object.entries(atrule.descriptors)) { - const patch = patches.atrules[name].descriptors[descriptor_name]; + if (atrule.prelude) { + const patch = patches.atrules[name].prelude; if (!patch) { continue; } @@ -59,7 +59,7 @@ for (const [name, atrule] of Object.entries(patches.atrules)) { for (const test of (patch.tests.passing ?? [])) { try { - const result = forkedLexer.matchAtruleDescriptor(name, descriptor_name, test.value); + const result = forkedLexer.matchAtrulePrelude(name, test.prelude); if (!result.error) { continue; } @@ -67,13 +67,13 @@ for (const [name, atrule] of Object.entries(patches.atrules)) { } catch { } // eslint-disable-next-line no-console - console.log(`Expected no error for '@${name}' and '${descriptor_name}: ${test.value}'`); + console.log(`Expected no error for '@${name} ${test.prelude}'`); flaws++; } for (const test of (patch.tests.failing ?? [])) { try { - const result = forkedLexer.matchAtruleDescriptor(name, descriptor_name, test.value); + const result = forkedLexer.matchAtrulePrelude(name, test.prelude); if (result.error) { continue; } @@ -82,10 +82,56 @@ for (const [name, atrule] of Object.entries(patches.atrules)) { } // eslint-disable-next-line no-console - console.log(`Expected an error for '@${name}' and '${descriptor_name}: ${test.value}'`); + console.log(`Expected an error for '@${name} ${test.prelude}'`); flaws++; } } + + if (atrule.descriptors) { + for (const [descriptor_name] of Object.entries(atrule.descriptors)) { + const patch = patches.atrules[name].descriptors[descriptor_name]; + if (!patch) { + continue; + } + + if (patch.omit) { + continue; + } + + if (!patch.tests) { + continue; + } + + for (const test of (patch.tests.passing ?? [])) { + try { + const result = forkedLexer.matchAtruleDescriptor(name, descriptor_name, test.value); + if (!result.error) { + continue; + } + + } catch { } + + // eslint-disable-next-line no-console + console.log(`Expected no error for '@${name}' and '${descriptor_name}: ${test.value}'`); + flaws++; + } + + for (const test of (patch.tests.failing ?? [])) { + try { + const result = forkedLexer.matchAtruleDescriptor(name, descriptor_name, test.value); + if (result.error) { + continue; + } + } catch { + continue; + } + + // eslint-disable-next-line no-console + console.log(`Expected an error for '@${name}' and '${descriptor_name}: ${test.value}'`); + flaws++; + } + } + } } // Properties