Skip to content

Commit 256e277

Browse files
authored
Ensure some generic property descriptors are case-sensitive
This detects which syntax definitions are case-sensitive at prepare-time, and uses that to generate the correct generic property descriptors. Fixes #303. Closes #304 by superseding it.
1 parent 3b5719a commit 256e277

3 files changed

Lines changed: 91 additions & 9 deletions

File tree

lib/utils/propertyDescriptors.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,24 @@ const { AST_TYPES } = parsers;
1111
* simple properties, but properties with more complex grammars will need their own handlers.
1212
*
1313
* @param {string} property - The canonical CSS property name (e.g. "backdrop-filter", not "backdropFilter").
14+
* @param {object} opts - The options object.
15+
* @param {boolean} opts.caseSensitive - True if value is case-sensitive, false otherwise.
1416
* @returns {object} The property descriptor object.
1517
*/
16-
function createGenericPropertyDescriptor(property) {
18+
function createGenericPropertyDescriptor(property, { caseSensitive }) {
1719
return {
1820
set(v) {
1921
const value = parsers.prepareValue(v);
2022
if (parsers.hasVarFunc(value)) {
2123
this._setProperty(property, value);
2224
} else {
23-
const parsedValue = parsers.parsePropertyValue(property, v);
25+
const parsedValue = parsers.parsePropertyValue(property, v, {
26+
caseSensitive
27+
});
2428
if (Array.isArray(parsedValue)) {
2529
if (parsedValue.length === 1) {
2630
const [{ name, type, value: itemValue }] = parsedValue;
31+
// TODO: Add handlers for AST_TYPES.DIMENSION, AST_TYPES.PERCENTAGE etc.
2732
switch (type) {
2833
case AST_TYPES.CALC: {
2934
this._setProperty(property, `${name}(${itemValue})`);

scripts/generatePropertyDefinitions.mjs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import fs from "node:fs";
22
import path from "node:path";
33
import css from "@webref/css";
4+
import { definitionSyntax } from "css-tree";
45

5-
const { properties } = await css.listAll();
6-
const definitions = properties.map((definition) => [definition.name, definition]);
6+
const caseSensitiveTypes = ["custom-ident", "dashed-ident", "string"];
7+
const typeList = new Set([...caseSensitiveTypes]);
8+
9+
const { properties, types } = await css.listAll();
10+
const syntaxes = new Map([...types.map((definition) => [`<${definition.name}>`, definition])]);
11+
const definitions = properties.map(prepareDefinition);
712

813
const [dateTodayFormatted] = new Date().toISOString().split("T");
914
const output = `"use strict";
@@ -14,3 +19,67 @@ module.exports = new Map(${JSON.stringify(definitions, null, 2)});
1419

1520
const { dirname } = import.meta;
1621
fs.writeFileSync(path.resolve(dirname, "../lib/generated/propertyDefinitions.js"), output);
22+
23+
/**
24+
* Transforms the raw property definitions in @webref/css into the format needed for css-tree and jsdom,
25+
* while removing unnecessary fields.
26+
*
27+
* @param {object} definition - The CSS property definition object.
28+
* @returns {Array} A key-value pair consisting of the property name and the property definition.
29+
*/
30+
function prepareDefinition(definition) {
31+
const { name, syntax } = definition;
32+
if (syntax) {
33+
const collectedTypes = collectTypes(syntax);
34+
if (collectedTypes.size) {
35+
for (const type of caseSensitiveTypes) {
36+
if (collectedTypes.has(type)) {
37+
definition.caseSensitive = true;
38+
break;
39+
}
40+
}
41+
}
42+
}
43+
delete definition.extended;
44+
return [name, definition];
45+
}
46+
47+
/**
48+
* Collects CSS data types from syntax.
49+
* NOTE: Returns a map as we plan to collect dimensions etc. in the near future.
50+
*
51+
* @param {string} syntax - The CSS syntax definition.
52+
* @returns {Map} The collection of types.
53+
*/
54+
function collectTypes(syntax) {
55+
const collectedTypes = new Map();
56+
const ast = definitionSyntax.parse(syntax);
57+
if (ast.type === "Group") {
58+
const subList = new Set();
59+
definitionSyntax.walk(ast, {
60+
enter(node) {
61+
if (node.type === "Type") {
62+
if (typeList.has(node.name)) {
63+
collectedTypes.set(node.name, node);
64+
} else if (!node.name.endsWith("()")) {
65+
subList.add(node.name);
66+
}
67+
}
68+
}
69+
});
70+
if (subList.size) {
71+
for (const type of subList) {
72+
const { syntax: typeSyntax } = syntaxes.get(`<${type}>`);
73+
if (typeSyntax) {
74+
const expandedList = collectTypes(typeSyntax);
75+
if (expandedList.size) {
76+
for (const [key, value] of expandedList) {
77+
collectedTypes.set(key, value);
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}
84+
return collectedTypes;
85+
}

scripts/generatePropertyDescriptors.mjs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const implementedProperties = new Map(list);
1111

1212
const requires = [];
1313
const descriptors = [];
14-
for (const [canonicalProperty, { legacyAliasOf, styleDeclaration }] of propertyDefinitions) {
14+
for (const [canonicalProperty, { caseSensitive, legacyAliasOf, styleDeclaration }] of propertyDefinitions) {
1515
const camelizedProperty = dashedToCamelCase(canonicalProperty);
1616
const camelizedAliasProperty = legacyAliasOf ? dashedToCamelCase(legacyAliasOf) : "";
1717
if (implementedProperties.has(camelizedProperty)) {
@@ -28,8 +28,11 @@ for (const [canonicalProperty, { legacyAliasOf, styleDeclaration }] of propertyD
2828
descriptors.push(`"${property}": ${camelizedAliasProperty}.descriptor`);
2929
}
3030
} else {
31+
const opts = JSON.stringify({
32+
caseSensitive: caseSensitive ?? false
33+
});
3134
for (const property of styleDeclaration) {
32-
descriptors.push(`"${property}": createGenericPropertyDescriptor("${canonicalProperty}")`);
35+
descriptors.push(`"${property}": createGenericPropertyDescriptor("${canonicalProperty}", ${opts})`);
3336
}
3437
}
3538
}
@@ -48,9 +51,14 @@ module.exports = {
4851

4952
fs.writeFileSync(path.resolve(dirname, "../lib/generated/propertyDescriptors.js"), output);
5053

51-
// Utility to translate from `border-width` to `borderWidth`.
52-
// NOTE: For values prefixed with webkit, e.g. `-webkit-foo`, we need to provide
53-
// both `webkitFoo` and `WebkitFoo`. Here we only return `webkitFoo`.
54+
/**
55+
* Utility to translate from `border-width` to `borderWidth`.
56+
* NOTE: For values prefixed with webkit, e.g. `-webkit-foo`, we need to provide
57+
* both `webkitFoo` and `WebkitFoo`. Here we only return `webkitFoo`.
58+
*
59+
* @param {string} dashed - The dashed property name.
60+
* @returns {string} The camel cased property name.
61+
*/
5462
function dashedToCamelCase(dashed) {
5563
if (dashed.startsWith("--")) {
5664
return dashed;

0 commit comments

Comments
 (0)