From 3f88002934908ca1ea06e73131c8b8ac4392a2e3 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Fri, 10 Sep 2021 16:42:10 -0700 Subject: [PATCH 01/29] New CSSSyntax macro --- kumascript/macros/CSSSyntax.ejs | 605 +++++++++++--------------------- package.json | 2 + yarn.lock | 13 + 3 files changed, 211 insertions(+), 409 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 3f24537ee938..38d51769fe0e 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -1,442 +1,229 @@ <% -/* - Displays the syntax of a CSS property or descriptor - $0 - property/descriptor name - defaults to the slug name - $1 - @-rule - defaults to the @-rule within the URL -*/ +const slug = env.slug; +const locale = env.locale; +const name = $0 || slug.split('/').pop().toLowerCase(); -const mdnDataCSS = require('mdn-data/css'); -// List types for which we want to link to a description instead of -// including it in the formal syntax block -let s_named_color_link = mdn.localString({ - "en-US": "/docs/Web/CSS/color_value#Color_keywords", - "fr" : "/docs/Web/CSS/Type_color#Les_mots-clés", - "de" : "/docs/Web/CSS/Farben#Farbschlüsselwörter", - "es" : "/docs/Web/CSS/color_value#Color_keywords", - "ja" : "/docs/Web/CSS/color_value#Color_keywords", - "nl" : "/docs/Web/CSS/kleur_waarde#Color_keywords", - "pt-BR": "/docs/Web/CSS/color_value#Palavras-chave_de_cores", - "zh-CN": "/docs/Web/CSS/color_value#色彩关键字" -}); -let s_system_color_link = mdn.localString({ - "en-US": "/docs/Web/CSS/color_value#Color_keywords", - "fr" : "/docs/Web/CSS/Type_color#Couleurs_système", - "de" : "/docs/Web/CSS/Farben#Systemfarben", - "es" : "/docs/Web/CSS/color_value#Colores_de_Sistema", - "ja" : "/docs/Web/CSS/color_value#System_Colors", - "nl" : "/docs/Web/CSS/kleur_waarde#System_Colors", - "pt-BR": "/docs/Web/CSS/color_value#System_Colors", - "zh-CN": "/docs/Web/CSS/color_value#系统颜色" -}); -let externallyDescribedTypesData = { - "named-color": { - link: s_named_color_link - }, - "deprecated-system-color": { - link: s_system_color_link - } -}; -let data = { - ...mdnDataCSS, - externallyDescribedTypes: externallyDescribedTypesData, -}; -let slug = env.slug; -let locale = env.locale; -let name = $0 || (slug ? slug.split("/").pop().toLowerCase() : "preview-wiki-content"); -// "Conflicting" documents have an MD5 postfix to deal with multiple conflicting documents. -// In here we need a clean name so remove the MD5 part. -name = name.replace(/(.*)_[a-f0-9]{32}$/, (_, x) => x); -let atRule = $1; -let rawSyntax = ""; -let formattedSyntax = ""; -let localize = mdn.getLocalString; - -let localStrings = web.getJSONData("L10n-CSS"); - -let s_where = mdn.localString({ - "en-US": "where ", - "de" : "wobei ", - "fr" : "où ", - "ja" : "ここで", - "ru" : "где " -}); - -let s_syntax_value_definition = mdn.localString({ - "en-US": "Value_definition_syntax", - "de" : "Wertdefinitionssyntax", - "fr" : "Syntaxe_de_d%C3%A9finition_des_valeurs" -}); - -let s_or = mdn.localString({ - "en-US": "or", - "de" : "oder", - "ru" : "или" -}); - -let s_possible_values = mdn.localString({ - "en-US": "Possible values: ", - "de" : "Mögliche Werte: ", - "ja" : "使用可能な値: ", - "ru" : "Возможные значения: " -}); - -let s_number_followed_by = mdn.localString({ - "en-US": "a number followed by", - "de" : "eine Nummer gefolgt von", -}); - -let s_like = mdn.localString({ - "en-US": "like", - "de" : "wie", - "ru" : "как" -}); - -let typeInfo = { - "angle": { - "title": s_possible_values + s_number_followed_by + "'deg', 'grad', 'rad' " + s_or + " 'turn', " + s_like + " 2turn, 1.3rad, -60deg " + s_or + " 0grad." - }, - "blend-mode": { - "title": s_possible_values + "normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity" - }, - "color": { - "typeLinkName": "color_value" - }, - "custom-ident": {}, - "filter-function": { - "title": s_possible_values + "blur(), brightness(), contrast(), drop-shadow(), grayscale(), hue-rotate(), invert(), opacity(), saturate(), sepia()" - }, - "flex": { - "typeLinkName": "flex_value" - }, - "frequency": {}, - "ident": {}, - "image": {}, - "integer": {}, - "length": { - "title": s_possible_values + s_number_followed_by + "'em', 'ex', 'ch', 'rem', 'px', 'cm', 'mm', 'in', 'vh', 'vw', 'vmin', 'vmax', 'pt', 'pc' " + s_or + " 'px', " + s_like + " 3px, 1.5cm, -0.5em " + s_or + " 0" - }, - "number": {}, - "percentage": {}, - "position": { - "typeLinkName": "position_value" - }, - "resolution": {}, - "single-transition-timing-function": { - "title": s_possible_values + "cubic-bezier(), steps(), linear, ease, ease-in, ease-out, east-in-out, step-start-step-end" - }, - "string": {}, - "time": { - "title": s_possible_values + s_number_followed_by + "'s' " + s_or + " 'ms', " + s_like + " 3s, -2.5ms " + s_or + " 0s." - }, - "timing-function": { - "title": s_possible_values + "cubic-bezier(), steps(), linear, ease, ease-in, ease-out, east-in-out, step-start-step-end" - }, - "url": {}, - "uri": {}, - "transform-function": { - "title": s_possible_values + "matrix(), matrix3d(), rotate(), rotate3d(), rotateX(), rotateY(), rotateZ(), scale(), scale3d(), scaleX(), scaleY(), scaleZ(), skewX(), skewY(), translate(), translate3d(), translateX(), translateY(), translateZ()" - } -}; - -let operators = [ - { - regexp: /#/g, - title: mdn.localString({ - "en-US": "Hash mark: the entity is repeated one or several times, each occurence separated by a comma", - "de": "Rautensymbol: Die Entität wird einmal oder mehrmals wiederholt, wobei jedes Vorkommen durch ein Komma getrennt wird" - }), - anchor: mdn.localString({ - "en-US": "Hash_mark", - "de" : "Rautensymbol_()" - }) - }, - { - regexp: /\|\|/g, - title: mdn.localString({ - "en-US": "Double bar: one or several of the entities must be present, in any order", - "de" : "Doppelter Balken: Eine oder mehrere der Entitäten muss angegeben werden, in beliebiger Reihenfolge" - }), - anchor: mdn.localString({ - "en-US": "Double_bar", - "de" : "Doppelter_Balken" - }) - }, - { - regexp: /[^|](\|)[^|]/g, - title: mdn.localString({ - "en-US": "Single bar: exactly one of the entities must be present", - "de" : "Einfacher Balken: Genau eine der Entitäten muss angegeben werden" - }), - anchor: mdn.localString({ - "en-US": "Single_bar", - "de" : "Einfacher_Balken" - }) - }, - { - regexp: /&&/g, - title: mdn.localString({ - "en-US": "Double ampersand: all of the entities must be present, in any order", - "de" : "Doppeltes Und-Zeichen: Alle Entitäten müssen angegeben werden, in beliebiger Reihenfolge" - }), - anchor: mdn.localString({ - "en-US": "Double_ampersand", - "de" : "Doppeltes_Und-Zeichen" - }) - }, - { - regexp: /\?/g, - title: mdn.localString({ - "en-US": "Question mark: the entity is optional", - "de" : "Fragezeichen: Die Entität ist optional" - }), - anchor: mdn.localString({ - "en-US": "Question_mark", - "de" : "Fragezeichen_()" - }) - }, - { - regexp: /([^/])\*(?!\/)/g, - title: mdn.localString({ - "en-US": "Asterisk: the entity may occur zero, one or several times", - "de" : "Asterisk: Die Entität kann keinmal, einmal oder mehrmals vorkommen" - }), - anchor: mdn.localString({ - "en-US": "Asterisk", - "de" : "Asterisk_(*)" - }) - }, - { - regexp: /\+/g, - title: mdn.localString({ - "en-US": "Plus: the entity may occur one or several times", - "de" : "Plus: Die Entität kann einmal oder mehrmals vorkommen" - }), - anchor: mdn.localString({ - "en-US": "Plus", - "de" : "Plus_()" - }) - }, - { - regexp: /\[|\]/g, - title: mdn.localString({ - "en-US": "Brackets: enclose several entities, combinators, and multipliers to transform them as a single component", - "de" : "Eckige Klammern: umschließen mehrere Entitäten, Kombinatoren und Multiplikatoren, um diese als eine gesamte Komponente zu behandeln" - }), - anchor: mdn.localString({ - "en-US": "Brackets", - "de" : "Eckige_Klammern" - }) - }, - { - regexp: /\{(?!\s)|(\d)\}/g, - title: mdn.localString({ - "en-US": "Curly braces: encloses two integers defining the minimal and maximal numbers of occurrences of the entity", - "de" : "Geschweifte Klammern: umschließen zwei Ganzzahlen, die die Minimal- und Maximalanzahl an Vorkommen der Entität angeben" - }), - anchor: mdn.localString({ - "en-US": "Curly_braces", - "de" : "Geschweifte_Klammern_(_)" - }) - }, - { - regexp: /!/g, - title: mdn.localString({ - "en-US": "Exclamation point: the group must produce at least one value", - "de" : "Ausrufezeichen: die Gruppe muss mindestens einen Wert erzeugen" - }), - anchor: mdn.localString({ - "en-US": "Exclamation_point", - "de" : "Ausrufezeichen_(!)" - }) - }, -]; +/** + * Populate `properties` and `valuespaces` from webref + */ +const webRefData = require('@webref/css'); +const { definitionSyntax } = require('css-tree'); -async function buildLink(match, type) { - var link = "/" + locale + "/docs/Web/CSS/"; - var title = ""; - var propertyName = type.match(/'(.+?)'/); +const parsedFiles = await webRefData.listAll(); +const allData = Object.values(parsedFiles); - // Handle property references like <'color'> - if (propertyName) { - if (data.properties[propertyName[1]]) { - title = data.properties[propertyName[1]].syntax; - } - return "<" + propertyName[0] + ">"; - - // Handle basic types - } else if (Object.prototype.hasOwnProperty.call(typeInfo, type)) { - var typeLinkName = typeInfo[type].typeLinkName || type; - - link += typeLinkName; - - if (typeInfo[type].title) { - title = typeInfo[type].title; - } else { - title = ""; - } - - // Handle types which we want to describe using a link to a different page - } else if (Object.prototype.hasOwnProperty.call(data.externallyDescribedTypes, type)) { - return "<" + type + ">"; - - // Handle advanced types having their syntax defined within the 'CSSData' template - } else if (data.syntaxes[type]) { - return "<" + type + ">"; +let properties = {}; +for (const spec of allData) { + properties = {...properties, ...spec.properties}; +} - // Handled advanced types having their syntax not defined - } else { - return "<" + type + ">"; - } +let valuespaces = {}; +for (const spec of allData) { + valuespaces = {...valuespaces, ...spec.valuespaces}; +} - return "<" + type + ">"; +/** + * Used for building links and tooltips for multipliers + */ +const multiplierInfo = { + '*': { + fragment: 'asterisk_', + tooltip: 'Asterisk: the entity may occur zero, one or several times' + }, + '+': { + fragment: 'plus_', + tooltip: 'Plus: the entity may occur one or several times' + }, + '?': { + fragment: 'question_mark_', + tooltip: 'Question mark: the entity is optional' + }, + '{}': { + fragment: 'curly_braces_', + tooltip: 'Curly braces: encloses two integers defining the minimal and maximal numbers of occurrences of the entity' + }, + '#': { + fragment: 'hash_mark_', + tooltip: 'Hash mark: the entity is repeated one or several times, each occurence separated by a comma' + }, + '!': { + fragment: 'exclamation_point_!', + tooltip: 'Exclamation point: the group must produce at least one value' + } } -function addBrackets(str) { - return str.match(/\(\)$/) ? str : str + "()"; +/** + * CSS classes for nodes, to apply syntax highlighting + */ +const cssClasses = { + 'Type': ' class=\'token property\'', + 'Keyword': ' class=\'token keyword\'', + 'Function': ' class=\'token function\'' } -async function formatSyntax(rawSyntax) { - if (rawSyntax === "") { - return ""; +/** + * Determines the markup to generate for a single node. + */ +function renderNode(name, node) { + switch (node.type) { + case 'Type': { + // encode < and > + name = name.replaceAll('<', '<'); + name = name.replaceAll('>', '>'); + // add CSS class + return `${name}`; } + case 'Multiplier': { + // link to the Value Definition Symtax and provide a tooltip + let key = name; + if (name.startsWith('{')) { + key = '{}'; + } + const info = multiplierInfo[key]; + return `${name}`; + } + case 'Keyword': + case 'Function': { + return `${name}`; + } + case 'Token': { + if (name === ')') { + // this is probably a closing bracket + return `${name}`; + } + } + } + return name; +} - var formattedSyntax = rawSyntax; - operators.forEach(function(operator) { - if (formattedSyntax.indexOf("\n") !== -1 && - operator.title === "Curly braces") { - return ""; - } - - formattedSyntax = formattedSyntax.replace(operator.regexp, - function(match, text) { - var linkText = match; - if (match.indexOf("*") !== -1) { - linkText = "*"; - } else if (match.indexOf("}") !== -1) { - linkText = "}"; - } else if (typeof text === "string") { - linkText = text; - } +/** + * Generate the markup for every term in a syntax definition, + * ensuring that the terms are visually aligned + */ +function renderTerms(terms, combinator) { + let output = ''; + const termArray = []; + const termTextLengths = []; + + for (const term of terms) { + // figure out the lengths of the translated terms, without markup + // this is just so we can align the terms properly + const termTextLength = definitionSyntax.generate(term).length; + termTextLengths.push(termTextLength); + + // get the translated terms, with markup + const termText = definitionSyntax.generate(term, { decorate: renderNode}); + termArray.push(termText); + } - var output = "" + linkText + ""; - if (linkText === "|") { - output = " " + output + " "; - } else if (linkText === "*" || linkText === "}") { - output = text + output; - } + let maxTermLength = Math.max(...termTextLengths); - return output; - }); - }); - formattedSyntax = await string.asyncReplace( - formattedSyntax, - /<([a-z0-9()'-]+?)>/gi, - buildLink - ); + // write out the translated terms, padding with spaces for alignment + // and separating terms using their combinator symbol + for (let i = 0; i < termArray.length; i++) { + const termText = termArray[i]; + const spaceCount = (maxTermLength + 2) - termTextLengths[i]; + // omit the combinator for the final term + const combinatorText = (i < termArray.length-1 ? combinator : ''); + output += ` ${termText}${Array(spaceCount).join(' ')}${combinatorText}
`; + } - return formattedSyntax; + return output; } -async function formatTypesSyntax(formattedSyntax, describedTypes) { - var formattedTypesSyntax = ""; - var types = []; - var typeAnchorAttributes = formattedSyntax.match(/href=".+?"/g); - if (typeAnchorAttributes) { - typeAnchorAttributes.forEach(function(typeAnchorAttribute) { - var type = typeAnchorAttribute.match(/href="(?:#|.*\/)(.+?)"/); - if (types.indexOf(type[1]) === -1 && - type[1].indexOf(s_syntax_value_definition) === -1) { - // Some data type page names have '_value' appended, - // which needs to be removed in order to find the syntax - var subType = type[1].replace("_value", ""); - // Describe this type if it exists in "syntaxes" and - // it *does not* exist in externallyDescribedTypes - if ((Object.prototype.hasOwnProperty.call(data.syntaxes, subType)) && - (!Object.prototype.hasOwnProperty.call(data.externallyDescribedTypes, subType))) { - types.push(subType); - } - } - }); +/** + * Render the syntax for a single type + */ +function renderSyntax(type, syntax) { + // write out the name of this type + const typeName = `<${type}>`; + let output = `${typeName} =
`; + + const ast = definitionSyntax.parse(syntax); + // if the combinator is ' ', write the complete type syntax in a single line + if (ast.combinator === ' ') { + output += renderTerms([ast], ast.combinator); + } else { + // otherwise write out each direct child in its own line + output += renderTerms(ast.terms, ast.combinator); + } - var typesSyntax = ""; - if (types.length > 0) { - for(let index = 0; index < types.length; index++) { - let type = types[index]; - // Avoid recursions by checking whether a type was already - // described before - // check whether https://github.com/mdn/data/pull/66 was merged - var syntax = data.syntaxes[type].syntax || data.syntaxes[type]; - if (describedTypes.indexOf(type) === -1) { - typesSyntax += "<" + type + - "> = " + await formatSyntax(syntax); - if (index < types.length - 1) { - typesSyntax += "
"; - } - describedTypes.push(type); - } - } + return output; +} - if (typesSyntax !== "") { - formattedTypesSyntax += "

" + s_where + - "
" + typesSyntax + - "

"; - } - } +/** + * Get names of all the types in a given set of syntaxes + */ +function getTypesForSyntaxes(syntaxes, constituents) { - return formattedTypesSyntax + await formatTypesSyntax(typesSyntax, describedTypes); + function processNode(node) { + if (node.type == 'Type' && + (constituents.indexOf(node.name) === -1)) { + constituents.push(node.name); } + } - return formattedTypesSyntax; -} - -if (!atRule) { - var matches = null; - if (slug) { - matches = slug.match(/\/CSS\/(@.+?)(?=\/)/); - } + for (const syntax of syntaxes) { + let ast = definitionSyntax.parse(syntax); + definitionSyntax.walk(ast, processNode); + } - if (matches) { - atRule = matches[1]; - } } -if (name === "preview-wiki-content") { - formattedSyntax = "" + - localize(localStrings, "info_in_preview_not_available") + ""; -} else { - if (atRule) { - if (data.atRules[atRule] && data.atRules[atRule].descriptors && data.atRules[atRule].descriptors[name]) { - rawSyntax = data.atRules[atRule].descriptors[name].syntax; - } - } else if (name[0] === "@") { - if (data.atRules[name] && data.atRules[name].syntax) { - rawSyntax = data.atRules[name].syntax; - } - } else if (name[0] === ":" && typeof data.selectors[name] !== 'undefined') { - rawSyntax = data.selectors[name].syntax; - } else if (data.properties[name]) { - rawSyntax = data.properties[name].syntax; - } else if (data.syntaxes[addBrackets(name)]) { - rawSyntax = data.syntaxes[addBrackets(name)].syntax; +/** + * Given an item (such as a CSS property), fetch all the types that participate + * in its formal syntax definition, either directly or transitively. + */ +function getConstituentTypes(item) { + const allConstituents = []; + let oldConstituentsLength = allConstituents.length; + // get all the types in the top-level syntax + let constituentSyntaxes = [properties[item].value]; + getTypesForSyntaxes(constituentSyntaxes, allConstituents); + + // while an iteration added more types... + while (allConstituents.length > oldConstituentsLength) { + // get the syntaxes for all newly added constituents, + // and then get the types in those syntaxes + constituentSyntaxes = []; + for (let constituent of allConstituents.slice(oldConstituentsLength)) { + + let constituentSyntaxEntry = valuespaces[`<${constituent}>`]; + + if (constituentSyntaxEntry && constituentSyntaxEntry.value) { + constituentSyntaxes.push(constituentSyntaxEntry.value); + } } - formattedSyntax = await formatSyntax(rawSyntax); - formattedSyntax += await formatTypesSyntax(formattedSyntax, []); + oldConstituentsLength = allConstituents.length; + getTypesForSyntaxes(constituentSyntaxes, allConstituents); + } + return allConstituents; } -let out = ''; +function writeFormalSyntax(item) { + let output = ''; + output += '
';
+
+  // write the syntax for the property
+  output += renderSyntax(item, properties[item].value);
+  output += '
'; + + // collect all the constituent types for the property + const types = getConstituentTypes(item); + // and write each one out + for (const type of types) { + if (valuespaces[`<${type}>`] && valuespaces[`<${type}>`].value) { + output += renderSyntax(type, valuespaces[`<${type}>`].value); + output += '
'; + } + } -if (!formattedSyntax) { - out = "

No syntax available

No value found in the database.

"; -} else { - const rtlLocales = ['ar', 'he', 'fa']; - const rtl = rtlLocales.includes(env.locale); - out = `${formattedSyntax}
` + output += ''; + return output; } -%><%- out %> +let output = writeFormalSyntax(name); +%> +<%-output%> diff --git a/package.json b/package.json index b15d75c39c00..6fa2ac9eed8d 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@caporal/core": "2.0.2", "@fast-csv/parse": "4.3.6", "@mdn/browser-compat-data": "4.0.2", + "@webref/css": "^2.0.10", "accept-language-parser": "1.5.0", "browser-specs": "^2.9.0", "chalk": "4.1.2", @@ -58,6 +59,7 @@ "compression": "1.7.4", "cookie": "0.4.1", "cookie-parser": "1.4.5", + "css-tree": "^1.1.3", "cssesc": "^3.0.0", "dayjs": "1.10.6", "dotenv": "10.0.0", diff --git a/yarn.lock b/yarn.lock index 956defab2438..8de6f9ca6f71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4541,6 +4541,11 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.5.2.tgz#ea584b637ff63c5a477f6f21604b5a205b72c9ec" integrity sha512-vgJ5OLWadI8aKjDlOH3rb+dYyPd2GTZuQC/Tihjct6F9GpXGZINo3Y/IVuZVTM1eDQB+/AOsjPUWH/WySDaXvw== +"@webref/css@^2.0.10": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@webref/css/-/css-2.0.10.tgz#2c4a7b0ad335c40390d8f943ce85ca88c062b6c9" + integrity sha512-i462yo6VdGE1Rmg0JZaCuVbRgIaJH2HoaSLpWKCc7lfFV8VovLvjRo1L+P7MDuUnK68peF8/X6c5LhyPQex5Hg== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -7041,6 +7046,14 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" From 62f70d73699da10ef8e8c11e02045fb723c6c13a Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Fri, 10 Sep 2021 20:06:14 -0700 Subject: [PATCH 02/29] Highlight properties --- kumascript/macros/CSSSyntax.ejs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 38d51769fe0e..b4a1553b0477 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -67,6 +67,9 @@ const cssClasses = { */ function renderNode(name, node) { switch (node.type) { + case 'Property': { + return `${name}`; + } case 'Type': { // encode < and > name = name.replaceAll('<', '<'); From 691f08333ba43c6226f1c4fe95d30022a7c866bc Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Fri, 10 Sep 2021 20:13:55 -0700 Subject: [PATCH 03/29] Call property class a property class --- kumascript/macros/CSSSyntax.ejs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index b4a1553b0477..7f15030a447e 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -57,7 +57,7 @@ const multiplierInfo = { * CSS classes for nodes, to apply syntax highlighting */ const cssClasses = { - 'Type': ' class=\'token property\'', + 'Property': ' class=\'token property\'', 'Keyword': ' class=\'token keyword\'', 'Function': ' class=\'token function\'' } @@ -68,14 +68,15 @@ const cssClasses = { function renderNode(name, node) { switch (node.type) { case 'Property': { - return `${name}`; + return `${name}`; } case 'Type': { // encode < and > name = name.replaceAll('<', '<'); name = name.replaceAll('>', '>'); // add CSS class - return `${name}`; + // we use "property" because there isn't one for types + return `${name}`; } case 'Multiplier': { // link to the Value Definition Symtax and provide a tooltip @@ -141,7 +142,7 @@ function renderTerms(terms, combinator) { function renderSyntax(type, syntax) { // write out the name of this type const typeName = `<${type}>`; - let output = `${typeName} =
`; + let output = `${typeName} =
`; const ast = definitionSyntax.parse(syntax); // if the combinator is ' ', write the complete type syntax in a single line From 7e48431458641ed8fdc157475e15daad27a6aa82 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Fri, 10 Sep 2021 20:32:10 -0700 Subject: [PATCH 04/29] Simplify CSS class stuff --- kumascript/macros/CSSSyntax.ejs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 7f15030a447e..cafe667063b7 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -53,22 +53,13 @@ const multiplierInfo = { } } -/** - * CSS classes for nodes, to apply syntax highlighting - */ -const cssClasses = { - 'Property': ' class=\'token property\'', - 'Keyword': ' class=\'token keyword\'', - 'Function': ' class=\'token function\'' -} - /** * Determines the markup to generate for a single node. */ function renderNode(name, node) { switch (node.type) { case 'Property': { - return `${name}`; + return `${name}`; } case 'Type': { // encode < and > @@ -76,7 +67,7 @@ function renderNode(name, node) { name = name.replaceAll('>', '>'); // add CSS class // we use "property" because there isn't one for types - return `${name}`; + return `${name}`; } case 'Multiplier': { // link to the Value Definition Symtax and provide a tooltip @@ -87,14 +78,16 @@ function renderNode(name, node) { const info = multiplierInfo[key]; return `${name}`; } - case 'Keyword': + case 'Keyword': { + return `${name}`; + } case 'Function': { - return `${name}`; + return `${name}`; } case 'Token': { if (name === ')') { // this is probably a closing bracket - return `${name}`; + return `${name}`; } } } @@ -142,7 +135,7 @@ function renderTerms(terms, combinator) { function renderSyntax(type, syntax) { // write out the name of this type const typeName = `<${type}>`; - let output = `${typeName} =
`; + let output = `${typeName} =
`; const ast = definitionSyntax.parse(syntax); // if the combinator is ' ', write the complete type syntax in a single line From 87ee047c0ebf2bd5dfa5e7853a17b9e93e9be19e Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Sun, 12 Sep 2021 11:49:58 -0700 Subject: [PATCH 05/29] Link to externally described types --- kumascript/macros/CSSSyntax.ejs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index cafe667063b7..e4f31287ff08 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -63,11 +63,15 @@ function renderNode(name, node) { } case 'Type': { // encode < and > - name = name.replaceAll('<', '<'); - name = name.replaceAll('>', '>'); - // add CSS class - // we use "property" because there isn't one for types - return `${name}`; + let encoded = name.replaceAll('<', '<'); + encoded = encoded.replaceAll('>', '>'); + // add CSS class: we use "property" because there isn't one for types + const span = `${encoded}`; + // if this type is not included in the synax, link to its dedicated page + if (valuespaces[name] && valuespaces[name].value) { + return span; + } + return `${span}`; } case 'Multiplier': { // link to the Value Definition Symtax and provide a tooltip @@ -155,7 +159,7 @@ function renderSyntax(type, syntax) { function getTypesForSyntaxes(syntaxes, constituents) { function processNode(node) { - if (node.type == 'Type' && + if (node.type === 'Type' && (constituents.indexOf(node.name) === -1)) { constituents.push(node.name); } @@ -213,7 +217,6 @@ function writeFormalSyntax(item) { for (const type of types) { if (valuespaces[`<${type}>`] && valuespaces[`<${type}>`].value) { output += renderSyntax(type, valuespaces[`<${type}>`].value); - output += '
'; } } From 2690dcaf50d838c47a74ccb7e8c1ecc9950654cd Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Mon, 13 Sep 2021 19:33:48 -0700 Subject: [PATCH 06/29] Linkify brackets and combinators --- kumascript/macros/CSSSyntax.ejs | 49 +++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index e4f31287ff08..c57abb02a5cf 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -4,6 +4,7 @@ const slug = env.slug; const locale = env.locale; const name = $0 || slug.split('/').pop().toLowerCase(); +const valueDefinitionUrl = `/${locale}/docs/Web/CSS/Value_definition_syntax`; /** * Populate `properties` and `valuespaces` from webref */ @@ -24,9 +25,9 @@ for (const spec of allData) { } /** - * Used for building links and tooltips for multipliers + * Used for building links and tooltips for parts of the value definition syntax */ -const multiplierInfo = { +const syntaxDescriptions = { '*': { fragment: 'asterisk_', tooltip: 'Asterisk: the entity may occur zero, one or several times' @@ -50,6 +51,22 @@ const multiplierInfo = { '!': { fragment: 'exclamation_point_!', tooltip: 'Exclamation point: the group must produce at least one value' + }, + '[]': { + fragment: 'brackets', + tooltip: 'Brackets: enclose several entities, combinators, and multipliers to transform them as a single component' + }, + '|': { + fragment: 'single_bar', + tooltip: 'Single bar: exactly one of the entities must be present' + }, + '||': { + fragment: 'double_bar', + tooltip: 'Double bar: one or several of the entities must be present, in any order' + }, + '&&': { + fragment: 'double_ampersand', + tooltip: 'Double ampersand: all of the entities must be present, in any order' } } @@ -79,8 +96,8 @@ function renderNode(name, node) { if (name.startsWith('{')) { key = '{}'; } - const info = multiplierInfo[key]; - return `${name}`; + const info = syntaxDescriptions[key]; + return `${name}`; } case 'Keyword': { return `${name}`; @@ -94,6 +111,21 @@ function renderNode(name, node) { return `${name}`; } } + case 'Group': { + // link from brackets to the value definition syntax docs + const info = syntaxDescriptions['[]']; + name = name.replace(/^\[/, `[`); + name = name.replace(/\]$/, `]`); + + // link from combinators (except " ") to the value definition syntax docs + if (node.combinator && (node.combinator !== ' ')) { + const info = syntaxDescriptions[node.combinator]; + // note that we are replacing the combinator surrounded by spaces, like " | " + name = name.replaceAll(` ${node.combinator} `, ` ${node.combinator} `); + } + + return name; + } } return name; } @@ -125,8 +157,14 @@ function renderTerms(terms, combinator) { for (let i = 0; i < termArray.length; i++) { const termText = termArray[i]; const spaceCount = (maxTermLength + 2) - termTextLengths[i]; + let combinatorText = ''; + if (combinator && combinator !== " ") { + const info = syntaxDescriptions[combinator]; + // link from combinators (except " ") to the value definition syntax docs + combinatorText = `${combinator}`; + } // omit the combinator for the final term - const combinatorText = (i < termArray.length-1 ? combinator : ''); + combinatorText = (i < termArray.length-1 ? combinatorText : ''); output += ` ${termText}${Array(spaceCount).join(' ')}${combinatorText}
`; } @@ -217,6 +255,7 @@ function writeFormalSyntax(item) { for (const type of types) { if (valuespaces[`<${type}>`] && valuespaces[`<${type}>`].value) { output += renderSyntax(type, valuespaces[`<${type}>`].value); + output += '
'; } } From 8b8a8bb0813bde4b0cb3c503cf00dff5662039fa Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Tue, 15 Mar 2022 21:28:51 -0700 Subject: [PATCH 07/29] Use browser-compat to figure out which spec to get the data from --- kumascript/macros/CSSSyntax.ejs | 72 +++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index c57abb02a5cf..253c8d2801db 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -1,29 +1,72 @@ <% +const bcd = require('@mdn/browser-compat-data'); +const webRefData = require('@webref/css'); +const { definitionSyntax } = require('css-tree'); + const slug = env.slug; const locale = env.locale; const name = $0 || slug.split('/').pop().toLowerCase(); const valueDefinitionUrl = `/${locale}/docs/Web/CSS/Value_definition_syntax`; -/** - * Populate `properties` and `valuespaces` from webref - */ -const webRefData = require('@webref/css'); -const { definitionSyntax } = require('css-tree'); -const parsedFiles = await webRefData.listAll(); -const allData = Object.values(parsedFiles); +// returns an array of one or more spec URLs, based on the browser-compat front matter key +function getSpecURLs() { + + function packageBCD(query) { + const data = query.split(".").reduce((prev, curr) => { + return prev && Object.prototype.hasOwnProperty.call(prev, curr) + ? prev[curr] + : undefined; + }, bcd); + return data; + } + + // look for the browser-compat front matter key + const bcdQuery = $0 || env['browser-compat']; + if (!bcdQuery) { + throw new Error("No 'browser-compat' front-matter value found"); + } -let properties = {}; -for (const spec of allData) { - properties = {...properties, ...spec.properties}; + // get the BCD JSON from the key + const bcdJSON = packageBCD(bcdQuery); + // can be a single URL or an array, normalize it as an array + const specURLsWithFragment = Array.isArray(bcdJSON.__compat.spec_url) + ? bcdJSON.__compat.spec_url + : [bcdJSON.__compat.spec_url]; + + // remove the fragment + const specURLs = specURLsWithFragment.map(specURLWithFragment => { + const url = new URL(specURLWithFragment); + return `${url.origin}${url.pathname}`; + }); + + return specURLs; } -let valuespaces = {}; -for (const spec of allData) { - valuespaces = {...valuespaces, ...spec.valuespaces}; +async function getSpecData(specURLs) { + const parsedFiles = await webRefData.listAll(); + const allData = Object.values(parsedFiles); + + const specs = []; + for (const data of allData) { + if (specURLs.includes(data.spec.url) && data.properties[name]) { + specs.push({ + properties: data.properties, + values: data.valuespaces + }); + } + } + + return specs; } +const specURLs = getSpecURLs(); +const specs = await getSpecData(specURLs); + +const properties = specs[0].properties; +const valuespaces = specs[0].values; + /** * Used for building links and tooltips for parts of the value definition syntax */ @@ -144,7 +187,6 @@ function renderTerms(terms, combinator) { // this is just so we can align the terms properly const termTextLength = definitionSyntax.generate(term).length; termTextLengths.push(termTextLength); - // get the translated terms, with markup const termText = definitionSyntax.generate(term, { decorate: renderNode}); termArray.push(termText); @@ -244,11 +286,9 @@ function getConstituentTypes(item) { function writeFormalSyntax(item) { let output = ''; output += '
';
-
   // write the syntax for the property
   output += renderSyntax(item, properties[item].value);
   output += '
'; - // collect all the constituent types for the property const types = getConstituentTypes(item); // and write each one out From 448a72d860810a018df68cdb87bed7882ede9264 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 16 Mar 2022 14:56:02 -0700 Subject: [PATCH 08/29] Random updates --- kumascript/macros/CSSSyntax.ejs | 51 +++++++++++++++++---------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 253c8d2801db..2772396906b5 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -6,10 +6,14 @@ const { definitionSyntax } = require('css-tree'); const slug = env.slug; const locale = env.locale; -const name = $0 || slug.split('/').pop().toLowerCase(); +const bcdQuery = env['browser-compat']; const valueDefinitionUrl = `/${locale}/docs/Web/CSS/Value_definition_syntax`; +// get the contents of webref +const parsedFiles = await webRefData.listAll(); +const allData = Object.values(parsedFiles); + // returns an array of one or more spec URLs, based on the browser-compat front matter key function getSpecURLs() { @@ -23,7 +27,6 @@ function getSpecURLs() { } // look for the browser-compat front matter key - const bcdQuery = $0 || env['browser-compat']; if (!bcdQuery) { throw new Error("No 'browser-compat' front-matter value found"); } @@ -44,28 +47,27 @@ function getSpecURLs() { return specURLs; } -async function getSpecData(specURLs) { - const parsedFiles = await webRefData.listAll(); - const allData = Object.values(parsedFiles); - - const specs = []; - for (const data of allData) { - if (specURLs.includes(data.spec.url) && data.properties[name]) { - specs.push({ - properties: data.properties, - values: data.valuespaces - }); +function getPropertySyntax(specs, property, webrefData) { + // return the first property syntax we find in the given specs + for (const data of webrefData) { + if (specs.includes(data.spec.url) && data.properties[property]) { + return data.properties[property].value; } } - - return specs; } +// get the spec URLs from BCD, to help us find the spec in webref const specURLs = getSpecURLs(); -const specs = await getSpecData(specURLs); -const properties = specs[0].properties; -const valuespaces = specs[0].values; +// get the property name from URL +const propertyName = $0 || slug.split('/').pop().toLowerCase(); + +// get the property syntax and valuespaces deom webref +const propertySyntax = getPropertySyntax(specURLs, propertyName, allData); +let valuespaces = {}; +for (const spec of allData) { + valuespaces = {...valuespaces, ...spec.valuespaces}; +} /** * Used for building links and tooltips for parts of the value definition syntax @@ -256,11 +258,11 @@ function getTypesForSyntaxes(syntaxes, constituents) { * Given an item (such as a CSS property), fetch all the types that participate * in its formal syntax definition, either directly or transitively. */ -function getConstituentTypes(item) { +function getConstituentTypes() { const allConstituents = []; let oldConstituentsLength = allConstituents.length; // get all the types in the top-level syntax - let constituentSyntaxes = [properties[item].value]; + let constituentSyntaxes = [propertySyntax]; getTypesForSyntaxes(constituentSyntaxes, allConstituents); // while an iteration added more types... @@ -283,14 +285,15 @@ function getConstituentTypes(item) { return allConstituents; } -function writeFormalSyntax(item) { +function writeFormalSyntax() { let output = ''; output += '
';
   // write the syntax for the property
-  output += renderSyntax(item, properties[item].value);
+  output += renderSyntax(propertyName, propertySyntax);
   output += '
'; // collect all the constituent types for the property - const types = getConstituentTypes(item); + const types = getConstituentTypes(); + // and write each one out for (const type of types) { if (valuespaces[`<${type}>`] && valuespaces[`<${type}>`].value) { @@ -303,6 +306,6 @@ function writeFormalSyntax(item) { return output; } -let output = writeFormalSyntax(name); +const output = writeFormalSyntax(); %> <%-output%> From b2ce00fd0052a6383d3fe37ad6c19ea8f87d9a95 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Thu, 28 Apr 2022 14:17:55 -0700 Subject: [PATCH 09/29] Resolve syntax only using webref --- kumascript/macros/CSSSyntax.ejs | 98 ++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 2772396906b5..a5261719a67c 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -1,71 +1,75 @@ <% -const bcd = require('@mdn/browser-compat-data'); const webRefData = require('@webref/css'); const { definitionSyntax } = require('css-tree'); const slug = env.slug; const locale = env.locale; -const bcdQuery = env['browser-compat']; const valueDefinitionUrl = `/${locale}/docs/Web/CSS/Value_definition_syntax`; // get the contents of webref -const parsedFiles = await webRefData.listAll(); -const allData = Object.values(parsedFiles); - -// returns an array of one or more spec URLs, based on the browser-compat front matter key -function getSpecURLs() { - - function packageBCD(query) { - const data = query.split(".").reduce((prev, curr) => { - return prev && Object.prototype.hasOwnProperty.call(prev, curr) - ? prev[curr] - : undefined; - }, bcd); - return data; - } +const parsedWebRef = await webRefData.listAll(); - // look for the browser-compat front matter key - if (!bcdQuery) { - throw new Error("No 'browser-compat' front-matter value found"); +/* + * Get *all* webref spec entries that contain this property + */ +function getSpecsForProp(parsedWebRef, propName) { + const specsForProp = []; + for (const [shortname, data] of Object.entries(parsedWebRef)) { + const propNames = Object.keys(data.properties); + if (propNames.includes(propName)) { + specsForProp.push(shortname); + } } - - // get the BCD JSON from the key - const bcdJSON = packageBCD(bcdQuery); - // can be a single URL or an array, normalize it as an array - const specURLsWithFragment = Array.isArray(bcdJSON.__compat.spec_url) - ? bcdJSON.__compat.spec_url - : [bcdJSON.__compat.spec_url]; - - // remove the fragment - const specURLs = specURLsWithFragment.map(specURLWithFragment => { - const url = new URL(specURLWithFragment); - return `${url.origin}${url.pathname}`; - }); - - return specURLs; + return specsForProp; } -function getPropertySyntax(specs, property, webrefData) { - // return the first property syntax we find in the given specs - for (const data of webrefData) { - if (specs.includes(data.spec.url) && data.properties[property]) { - return data.properties[property].value; +/* + * Given one or more specs that define a property, return a single + * syntax string for the property. + */ +function resolveSyntax(parsedWebRef, propName, specNames) { + if (specNames.length === 1) { + return parsedWebRef[specNames[0]].properties[propName].value; + } + // If there's more than one spec, + // assume that one of them is the base spec, which defines `values`, + // and the others define incremental additions as `newValues`, + // then concatenate new values on to values to return a single syntax string + let syntax = ''; + let newSyntaxes = ''; + for (const specName of specNames) { + const baseValue = parsedWebRef[specName].properties[propName].value; + if (baseValue) { + syntax = baseValue; + } + const newValues = parsedWebRef[specName].properties[propName].newValues; + if (newValues) { + newSyntaxes += ` | ${newValues}`; } } + return `${syntax}${newSyntaxes}`; } -// get the spec URLs from BCD, to help us find the spec in webref -const specURLs = getSpecURLs(); +function getPropertySyntax(propertyName, parsedWebRef) { + let specsForProp = getSpecsForProp(parsedWebRef, propertyName); + if (specsForProp.length > 1) { + // filter out specs that end "-n" where n is a number + specsForProp = specsForProp.filter( specName => !(/-\d+$/.test(specName)) ); + } + return resolveSyntax(parsedWebRef, propertyName, specsForProp); +} // get the property name from URL const propertyName = $0 || slug.split('/').pop().toLowerCase(); -// get the property syntax and valuespaces deom webref -const propertySyntax = getPropertySyntax(specURLs, propertyName, allData); +// get the syntax for this property +const propertySyntax = getPropertySyntax(propertyName, parsedWebRef); + +// get all the value syntaxes let valuespaces = {}; -for (const spec of allData) { +for (const spec of Object.values(parsedWebRef)) { valuespaces = {...valuespaces, ...spec.valuespaces}; } @@ -132,8 +136,12 @@ function renderNode(name, node) { // if this type is not included in the synax, link to its dedicated page if (valuespaces[name] && valuespaces[name].value) { return span; + } else { + // the slug does not include the angle brackets + let stripped = name.replaceAll('<', ''); + stripped = stripped.replaceAll('>', ''); + return `${span}`; } - return `${span}`; } case 'Multiplier': { // link to the Value Definition Symtax and provide a tooltip From 58f58fa344604ee85c27121f12e7260d9bbc9d89 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Fri, 29 Apr 2022 10:20:21 -0700 Subject: [PATCH 10/29] Fix links when value includes range --- kumascript/macros/CSSSyntax.ejs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index a5261719a67c..4c303e56fa9f 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -138,9 +138,10 @@ function renderNode(name, node) { return span; } else { // the slug does not include the angle brackets - let stripped = name.replaceAll('<', ''); - stripped = stripped.replaceAll('>', ''); - return `${span}`; + let slug = name.replaceAll('<', ''); + slug = slug.replaceAll('>', ''); + slug = slug.replace(/\[.*\]/, '') + return `${span}`; } } case 'Multiplier': { From 77aed76480c40ac5a72aca49885777c5e4fcb9aa Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 4 May 2022 10:46:58 -0700 Subject: [PATCH 11/29] Require new version of webref/css --- package.json | 4 ++-- yarn.lock | 25 +++++++++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index c68d1bf6b838..bd3f6d19dbb7 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@fast-csv/parse": "4.3.6", "@mdn/browser-compat-data": "4.1.19", "@use-it/interval": "^1.0.0", - "@webref/css": "^2.0.10", + "@webref/css": "^4.1.1", "accept-language-parser": "1.5.0", "browser-specs": "^3.10.0", "chalk": "4.1.2", @@ -70,7 +70,7 @@ "compression": "1.7.4", "cookie": "0.5.0", "cookie-parser": "1.4.6", - "css-tree": "^1.1.3", + "css-tree": "^2.1.0", "cssesc": "^3.0.0", "dayjs": "1.11.1", "dexie": "3.2.2", diff --git a/yarn.lock b/yarn.lock index d63db83a4a84..830a51671faf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2855,10 +2855,10 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe" integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw== -"@webref/css@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@webref/css/-/css-2.0.10.tgz#2c4a7b0ad335c40390d8f943ce85ca88c062b6c9" - integrity sha512-i462yo6VdGE1Rmg0JZaCuVbRgIaJH2HoaSLpWKCc7lfFV8VovLvjRo1L+P7MDuUnK68peF8/X6c5LhyPQex5Hg== +"@webref/css@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@webref/css/-/css-4.1.1.tgz#ed67ed325a31b400937c28944f1f26029c27d231" + integrity sha512-HDviqRnmuv2qfnx8SDP7EYNERy7Q9OP7YDo1RUgOmonhGhL/Z6hCGFwgY9AZSvnQFF4xXTk52Z8UKW+fFg8PUg== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -5206,13 +5206,13 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" -css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== +css-tree@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.1.0.tgz#170e27ccf94e7c5facb183765c25898be843d1d2" + integrity sha512-PcysZRzToBbrpoUrZ9qfblRIRf8zbEAkU0AIpQFtgkFK0vSbzOmBCvdSAx2Zg7Xx5wiYJKUKk0NMP7kxevie/A== dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" + mdn-data "2.0.27" + source-map-js "^1.0.1" css-what@2.1: version "2.1.3" @@ -15079,6 +15079,11 @@ source-map-explorer@^2.5.2: resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz" integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== +source-map-js@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: version "0.5.3" resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz" From 0fac685302f6869e1bc862c9176b5fb2559d056e Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 4 May 2022 10:47:24 -0700 Subject: [PATCH 12/29] Cleanup, reorg, comments --- kumascript/macros/CSSSyntax.ejs | 156 ++++++++++++++++---------------- 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 4c303e56fa9f..12e5def42e78 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -3,79 +3,12 @@ const webRefData = require('@webref/css'); const { definitionSyntax } = require('css-tree'); -const slug = env.slug; const locale = env.locale; +// URL where we describe value definition syntax const valueDefinitionUrl = `/${locale}/docs/Web/CSS/Value_definition_syntax`; -// get the contents of webref -const parsedWebRef = await webRefData.listAll(); - -/* - * Get *all* webref spec entries that contain this property - */ -function getSpecsForProp(parsedWebRef, propName) { - const specsForProp = []; - for (const [shortname, data] of Object.entries(parsedWebRef)) { - const propNames = Object.keys(data.properties); - if (propNames.includes(propName)) { - specsForProp.push(shortname); - } - } - return specsForProp; -} - -/* - * Given one or more specs that define a property, return a single - * syntax string for the property. - */ -function resolveSyntax(parsedWebRef, propName, specNames) { - if (specNames.length === 1) { - return parsedWebRef[specNames[0]].properties[propName].value; - } - // If there's more than one spec, - // assume that one of them is the base spec, which defines `values`, - // and the others define incremental additions as `newValues`, - // then concatenate new values on to values to return a single syntax string - let syntax = ''; - let newSyntaxes = ''; - for (const specName of specNames) { - const baseValue = parsedWebRef[specName].properties[propName].value; - if (baseValue) { - syntax = baseValue; - } - const newValues = parsedWebRef[specName].properties[propName].newValues; - if (newValues) { - newSyntaxes += ` | ${newValues}`; - } - } - return `${syntax}${newSyntaxes}`; -} - -function getPropertySyntax(propertyName, parsedWebRef) { - let specsForProp = getSpecsForProp(parsedWebRef, propertyName); - if (specsForProp.length > 1) { - // filter out specs that end "-n" where n is a number - specsForProp = specsForProp.filter( specName => !(/-\d+$/.test(specName)) ); - } - return resolveSyntax(parsedWebRef, propertyName, specsForProp); -} - -// get the property name from URL -const propertyName = $0 || slug.split('/').pop().toLowerCase(); - -// get the syntax for this property -const propertySyntax = getPropertySyntax(propertyName, parsedWebRef); - -// get all the value syntaxes -let valuespaces = {}; -for (const spec of Object.values(parsedWebRef)) { - valuespaces = {...valuespaces, ...spec.valuespaces}; -} - -/** - * Used for building links and tooltips for parts of the value definition syntax - */ +// Used for building links and tooltips for parts of the value definition syntax const syntaxDescriptions = { '*': { fragment: 'asterisk_', @@ -119,8 +52,62 @@ const syntaxDescriptions = { } } +// get the contents of webref +const parsedWebRef = await webRefData.listAll(); + +// get all the value syntaxes +let valuespaces = {}; +for (const spec of Object.values(parsedWebRef)) { + valuespaces = {...valuespaces, ...spec.valuespaces}; +} + +// get the property name from the page slug +const propertyName = $0 || env.slug.split('/').pop().toLowerCase(); + /** - * Determines the markup to generate for a single node. + * Get the formal syntax for a property from the webref data, given: + * `propertyName`: the name of the property + * `parsedWebRef`: the webref data + */ +function getPropertySyntax(propertyName, parsedWebRef) { + // 1) get all specs which list this property + let specsForProp = []; + for (const [shortname, data] of Object.entries(parsedWebRef)) { + const propNames = Object.keys(data.properties); + if (propNames.includes(propertyName)) { + specsForProp.push(shortname); + } + } + // 2) If we have more than one spec, filter out specs that end "-n" where n is a number + if (specsForProp.length > 1) { + specsForProp = specsForProp.filter( specName => !(/-\d+$/.test(specName)) ); + } + // 3) If we now have only one spec, return the syntax it lists + if (specsForProp.length === 1) { + return parsedWebRef[specsForProp[0]].properties[propertyName].value; + } + // 4) If we still have > 1 spec, assume that: + // - one of them is the base spec, which defines `values`, + // - the others define incremental additions as `newValues` + // Concatenate new values on to values to return a single syntax string + let syntax = ''; + let newSyntaxes = ''; + for (const specName of specsForProp) { + const baseValue = parsedWebRef[specName].properties[propertyName].value; + if (baseValue) { + syntax = baseValue; + } + const newValues = parsedWebRef[specName].properties[propertyName].newValues; + if (newValues) { + newSyntaxes += ` | ${newValues}`; + } + } + return syntax; +} + +/** + * Determines the markup to generate for a single node in the AST + * generated by css-tree. */ function renderNode(name, node) { switch (node.type) { @@ -133,7 +120,7 @@ function renderNode(name, node) { encoded = encoded.replaceAll('>', '>'); // add CSS class: we use "property" because there isn't one for types const span = `${encoded}`; - // if this type is not included in the synax, link to its dedicated page + // if this type is not included in the syntax, link to its dedicated page if (valuespaces[name] && valuespaces[name].value) { return span; } else { @@ -225,7 +212,7 @@ function renderTerms(terms, combinator) { } /** - * Render the syntax for a single type + * Render the syntax for a single type. */ function renderSyntax(type, syntax) { // write out the name of this type @@ -267,7 +254,7 @@ function getTypesForSyntaxes(syntaxes, constituents) { * Given an item (such as a CSS property), fetch all the types that participate * in its formal syntax definition, either directly or transitively. */ -function getConstituentTypes() { +function getConstituentTypes(propertySyntax) { const allConstituents = []; let oldConstituentsLength = allConstituents.length; // get all the types in the top-level syntax @@ -294,14 +281,21 @@ function getConstituentTypes() { return allConstituents; } -function writeFormalSyntax() { +/** + * Write out the complete formal syntax for a property. + * + * This includes the property's own syntax, described in `propertySyntax`, + * and also the syntax for any types that participate in the definition of + * the property. + */ +function writeFormalSyntax(propertySyntax) { let output = ''; output += '
';
   // write the syntax for the property
   output += renderSyntax(propertyName, propertySyntax);
   output += '
'; // collect all the constituent types for the property - const types = getConstituentTypes(); + const types = getConstituentTypes(propertySyntax); // and write each one out for (const type of types) { @@ -315,6 +309,16 @@ function writeFormalSyntax() { return output; } -const output = writeFormalSyntax(); +let output = ''; + +// get the syntax for this property +const propertySyntax = getPropertySyntax(propertyName, parsedWebRef); + +if (!propertySyntax) { + output = 'Error: could not find syntax for this item'; +} else { + // write it out + output = writeFormalSyntax(propertySyntax); +} %> <%-output%> From b838923e2d9a4014dc336578c69338a416bc1c97 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 4 May 2022 10:59:42 -0700 Subject: [PATCH 13/29] Don't use angled brackets for the property name --- kumascript/macros/CSSSyntax.ejs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 12e5def42e78..6431f383730f 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -216,8 +216,7 @@ function renderTerms(terms, combinator) { */ function renderSyntax(type, syntax) { // write out the name of this type - const typeName = `<${type}>`; - let output = `${typeName} =
`; + let output = `${type} =
`; const ast = definitionSyntax.parse(syntax); // if the combinator is ' ', write the complete type syntax in a single line @@ -300,7 +299,7 @@ function writeFormalSyntax(propertySyntax) { // and write each one out for (const type of types) { if (valuespaces[`<${type}>`] && valuespaces[`<${type}>`].value) { - output += renderSyntax(type, valuespaces[`<${type}>`].value); + output += renderSyntax(`<${type}>`, valuespaces[`<${type}>`].value); output += '
'; } } From b634812d0cbc74aa0a100c272919e378c49a4030 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 4 May 2022 11:03:44 -0700 Subject: [PATCH 14/29] Remove cssesc which had snuck back in --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index bd3f6d19dbb7..4be23a252901 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "cookie": "0.5.0", "cookie-parser": "1.4.6", "css-tree": "^2.1.0", - "cssesc": "^3.0.0", "dayjs": "1.11.1", "dexie": "3.2.2", "dotenv": "14.3.0", From 20281d2297b5f526008cba5828994e2656d338a7 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 20 May 2022 20:14:27 +0200 Subject: [PATCH 15/29] test: check flaws with toEqual() If we encounter flaws unexpectedly, this surfaces it in the output. --- testing/tests/developing.spec.js | 2 +- testing/tests/index.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/tests/developing.spec.js b/testing/tests/developing.spec.js index ded6dbf4abbe..4f6b1401cf10 100644 --- a/testing/tests/developing.spec.js +++ b/testing/tests/developing.spec.js @@ -70,7 +70,7 @@ test.describe("Testing the kitchensink page", () => { ).json(); expect(doc.title).toBe("The MDN Content Kitchensink"); - expect(Object.keys(doc.flaws).length).toBe(0); + expect(doc.flaws).toEqual({}); }); // XXX Do more advanced tasks that test the server and document "CRUD operations" diff --git a/testing/tests/index.test.js b/testing/tests/index.test.js index a70d612466c3..b6b41d2e45ab 100644 --- a/testing/tests/index.test.js +++ b/testing/tests/index.test.js @@ -1395,7 +1395,7 @@ test("img tags without 'src' should not crash", () => { ); const jsonFile = path.join(builtFolder, "index.json"); const { doc } = JSON.parse(fs.readFileSync(jsonFile)); - expect(Object.keys(doc.flaws).length).toBe(0); + expect(doc.flaws).toEqual({}); }); test("/Web/Embeddable should have 3 valid live samples", () => { @@ -1413,7 +1413,7 @@ test("/Web/Embeddable should have 3 valid live samples", () => { const jsonFile = path.join(builtFolder, "index.json"); const { doc } = JSON.parse(fs.readFileSync(jsonFile)); - expect(Object.keys(doc.flaws).length).toBe(0); + expect(doc.flaws).toEqual({}); const builtFiles = fs.readdirSync(path.join(builtFolder)); expect( From 2a78f82f28ed6eac183361daa42f0a3d585feb82 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 20 May 2022 20:23:17 +0200 Subject: [PATCH 16/29] fix(macros/CSSSyntax): use shim for String.prototype.replaceAll --- kumascript/macros/CSSSyntax.ejs | 11 ++++++----- package.json | 1 + yarn.lock | 12 ++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 6431f383730f..7a68db443749 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -2,6 +2,7 @@ const webRefData = require('@webref/css'); const { definitionSyntax } = require('css-tree'); +const replaceAll = require('string.prototype.replaceall'); const locale = env.locale; @@ -116,8 +117,8 @@ function renderNode(name, node) { } case 'Type': { // encode < and > - let encoded = name.replaceAll('<', '<'); - encoded = encoded.replaceAll('>', '>'); + let encoded = replaceAll(name, '<', '<'); + encoded = replaceAll(encoded, '>', '>'); // add CSS class: we use "property" because there isn't one for types const span = `${encoded}`; // if this type is not included in the syntax, link to its dedicated page @@ -125,8 +126,8 @@ function renderNode(name, node) { return span; } else { // the slug does not include the angle brackets - let slug = name.replaceAll('<', ''); - slug = slug.replaceAll('>', ''); + let slug = replaceAll(name, '<', ''); + slug = replaceAll(slug, '>', ''); slug = slug.replace(/\[.*\]/, '') return `${span}`; } @@ -162,7 +163,7 @@ function renderNode(name, node) { if (node.combinator && (node.combinator !== ' ')) { const info = syntaxDescriptions[node.combinator]; // note that we are replacing the combinator surrounded by spaces, like " | " - name = name.replaceAll(` ${node.combinator} `, ` ${node.combinator} `); + name = replaceAll(name, ` ${node.combinator} `, ` ${node.combinator} `); } return name; diff --git a/package.json b/package.json index 0efda6a018c3..a18e653d7352 100644 --- a/package.json +++ b/package.json @@ -181,6 +181,7 @@ "rough-notation": "^0.5.1", "sass": "^1.52.0", "source-map-explorer": "^2.5.2", + "string.prototype.replaceall": "^1.0.6", "style-dictionary": "^3.7.0", "stylelint": "^13.13.1", "stylelint-a11y": "^1.2.3", diff --git a/yarn.lock b/yarn.lock index 3e2710e6dfb1..bebf8568c602 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11555,6 +11555,18 @@ string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.7: regexp.prototype.flags "^1.4.1" side-channel "^1.0.4" +string.prototype.replaceall@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.replaceall/-/string.prototype.replaceall-1.0.6.tgz#566cba7c413713d0b1a85c5dba98b31f8db38196" + integrity sha512-OA8VDhE7ssNFlyoDXUHxw6V5cjgPrtosyJKqJX5i1P5tV9eUynsbhx1yz0g+Ye4fjFwAxhKLxt8GSRx2Aqc+Sw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + has-symbols "^1.0.2" + is-regex "^1.1.4" + string.prototype.trimend@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" From 45c6911007846caea66e528803e6c16496bc2bb6 Mon Sep 17 00:00:00 2001 From: wbamberg Date: Mon, 23 May 2022 16:48:24 -0700 Subject: [PATCH 17/29] Update kumascript/macros/CSSSyntax.ejs Co-authored-by: Claas Augner --- kumascript/macros/CSSSyntax.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 7a68db443749..a8082a55575f 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -67,8 +67,8 @@ const propertyName = $0 || env.slug.split('/').pop().toLowerCase(); /** * Get the formal syntax for a property from the webref data, given: - * `propertyName`: the name of the property - * `parsedWebRef`: the webref data + * @param {string} propertyName - the name of the property + * @param {object} parsedWebRef - the webref data */ function getPropertySyntax(propertyName, parsedWebRef) { // 1) get all specs which list this property From 166094c2ae4fcbc93ac4753a6cb6ee164888b093 Mon Sep 17 00:00:00 2001 From: wbamberg Date: Mon, 23 May 2022 16:48:33 -0700 Subject: [PATCH 18/29] Update kumascript/macros/CSSSyntax.ejs Co-authored-by: Claas Augner --- kumascript/macros/CSSSyntax.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index a8082a55575f..728047e59b16 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -238,7 +238,7 @@ function getTypesForSyntaxes(syntaxes, constituents) { function processNode(node) { if (node.type === 'Type' && - (constituents.indexOf(node.name) === -1)) { + (!constituents.includes(node.name))) { constituents.push(node.name); } } From 40e284af2a8b07ddf66a30aba37feebd4bfabf6d Mon Sep 17 00:00:00 2001 From: wbamberg Date: Mon, 23 May 2022 16:48:39 -0700 Subject: [PATCH 19/29] Update kumascript/macros/CSSSyntax.ejs Co-authored-by: Claas Augner --- kumascript/macros/CSSSyntax.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 728047e59b16..52df353a1da0 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -206,7 +206,7 @@ function renderTerms(terms, combinator) { } // omit the combinator for the final term combinatorText = (i < termArray.length-1 ? combinatorText : ''); - output += ` ${termText}${Array(spaceCount).join(' ')}${combinatorText}
`; + output += ` ${termText}${' '.repeat(spaceCount)}${combinatorText}
`; } return output; From 50d9305bb478465a584b9ea0cd8736e8d6cce281 Mon Sep 17 00:00:00 2001 From: wbamberg Date: Mon, 23 May 2022 16:48:49 -0700 Subject: [PATCH 20/29] Update kumascript/macros/CSSSyntax.ejs Co-authored-by: Claas Augner --- kumascript/macros/CSSSyntax.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 52df353a1da0..4720919da2b2 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -191,7 +191,7 @@ function renderTerms(terms, combinator) { termArray.push(termText); } - let maxTermLength = Math.max(...termTextLengths); + const maxTermLength = Math.max(...termTextLengths); // write out the translated terms, padding with spaces for alignment // and separating terms using their combinator symbol From e460ac4db6159be3fb733a90b273a65f56fc3db2 Mon Sep 17 00:00:00 2001 From: wbamberg Date: Mon, 23 May 2022 16:49:01 -0700 Subject: [PATCH 21/29] Update kumascript/macros/CSSSyntax.ejs Co-authored-by: Claas Augner --- kumascript/macros/CSSSyntax.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 4720919da2b2..2301e9dcd1e0 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -110,7 +110,7 @@ function getPropertySyntax(propertyName, parsedWebRef) { * Determines the markup to generate for a single node in the AST * generated by css-tree. */ -function renderNode(name, node) { +function renderNode(node, name) { switch (node.type) { case 'Property': { return `${name}`; From 72c877aa328969ca5af5a4327ba42fb79e9776dd Mon Sep 17 00:00:00 2001 From: wbamberg Date: Mon, 23 May 2022 16:49:10 -0700 Subject: [PATCH 22/29] Update kumascript/macros/CSSSyntax.ejs Co-authored-by: Claas Augner --- kumascript/macros/CSSSyntax.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 2301e9dcd1e0..d60e6406195b 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -70,7 +70,7 @@ const propertyName = $0 || env.slug.split('/').pop().toLowerCase(); * @param {string} propertyName - the name of the property * @param {object} parsedWebRef - the webref data */ -function getPropertySyntax(propertyName, parsedWebRef) { +function getPropertySyntax(propertyName) { // 1) get all specs which list this property let specsForProp = []; for (const [shortname, data] of Object.entries(parsedWebRef)) { From bcbd2f0657d5a310987c1abf22394695e8893790 Mon Sep 17 00:00:00 2001 From: wbamberg Date: Mon, 23 May 2022 16:49:36 -0700 Subject: [PATCH 23/29] Update kumascript/macros/CSSSyntax.ejs Co-authored-by: Claas Augner --- kumascript/macros/CSSSyntax.ejs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index d60e6406195b..1c58e854da78 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -168,8 +168,9 @@ function renderNode(node, name) { return name; } + default: + return name; } - return name; } /** From 7463b9bf3ad191ecf5ae50a02b8d4f3128448d92 Mon Sep 17 00:00:00 2001 From: wbamberg Date: Mon, 23 May 2022 16:49:53 -0700 Subject: [PATCH 24/29] Update kumascript/macros/CSSSyntax.ejs Co-authored-by: Claas Augner --- kumascript/macros/CSSSyntax.ejs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 1c58e854da78..52c9f8a9a7e0 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -257,13 +257,18 @@ function getTypesForSyntaxes(syntaxes, constituents) { */ function getConstituentTypes(propertySyntax) { const allConstituents = []; - let oldConstituentsLength = allConstituents.length; + let oldConstituentsLength = 0; // get all the types in the top-level syntax let constituentSyntaxes = [propertySyntax]; - getTypesForSyntaxes(constituentSyntaxes, allConstituents); // while an iteration added more types... - while (allConstituents.length > oldConstituentsLength) { + while (true) { + oldConstituentsLength = allConstituents.length; + getTypesForSyntaxes(constituentSyntaxes, allConstituents); + + if (allConstituents.length <= oldConstituentsLength) { + break; + } // get the syntaxes for all newly added constituents, // and then get the types in those syntaxes constituentSyntaxes = []; @@ -275,9 +280,6 @@ function getConstituentTypes(propertySyntax) { constituentSyntaxes.push(constituentSyntaxEntry.value); } } - - oldConstituentsLength = allConstituents.length; - getTypesForSyntaxes(constituentSyntaxes, allConstituents); } return allConstituents; } From 4b448f9a0c027b832aa3c0d1d2edd61747707d00 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Mon, 23 May 2022 17:59:43 -0700 Subject: [PATCH 25/29] Review comments --- kumascript/macros/CSSSyntax.ejs | 43 ++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 52c9f8a9a7e0..8fb9779efeb5 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -1,4 +1,12 @@ <% +/* + * Displays the formal syntax for a CSS property, using the values found in + * the https://github.com/w3c/webref package. + * + * The property name is taken from the page slug. + * + * The syntax is pretty-printed and syntax-highlighted. + */ const webRefData = require('@webref/css'); const { definitionSyntax } = require('css-tree'); @@ -12,23 +20,23 @@ const valueDefinitionUrl = `/${locale}/docs/Web/CSS/Value_definition_syntax`; // Used for building links and tooltips for parts of the value definition syntax const syntaxDescriptions = { '*': { - fragment: 'asterisk_', + fragment: 'asterisk', tooltip: 'Asterisk: the entity may occur zero, one or several times' }, '+': { - fragment: 'plus_', + fragment: 'plus', tooltip: 'Plus: the entity may occur one or several times' }, '?': { - fragment: 'question_mark_', + fragment: 'question_mark', tooltip: 'Question mark: the entity is optional' }, '{}': { - fragment: 'curly_braces_', + fragment: 'curly_braces', tooltip: 'Curly braces: encloses two integers defining the minimal and maximal numbers of occurrences of the entity' }, '#': { - fragment: 'hash_mark_', + fragment: 'hash_mark', tooltip: 'Hash mark: the entity is repeated one or several times, each occurence separated by a comma' }, '!': { @@ -110,7 +118,7 @@ function getPropertySyntax(propertyName) { * Determines the markup to generate for a single node in the AST * generated by css-tree. */ -function renderNode(node, name) { +function renderNode(name, node) { switch (node.type) { case 'Property': { return `${name}`; @@ -149,7 +157,7 @@ function renderNode(node, name) { } case 'Token': { if (name === ')') { - // this is probably a closing bracket + // this is a closing bracket return `${name}`; } } @@ -179,26 +187,27 @@ function renderNode(node, name) { */ function renderTerms(terms, combinator) { let output = ''; - const termArray = []; - const termTextLengths = []; + const renderedTerms = []; for (const term of terms) { // figure out the lengths of the translated terms, without markup // this is just so we can align the terms properly const termTextLength = definitionSyntax.generate(term).length; - termTextLengths.push(termTextLength); // get the translated terms, with markup const termText = definitionSyntax.generate(term, { decorate: renderNode}); - termArray.push(termText); + renderedTerms.push({ + text: termText, + length: termTextLength + }); } - const maxTermLength = Math.max(...termTextLengths); + const maxTermLength = Math.max(...renderedTerms.map(t => t.length)); // write out the translated terms, padding with spaces for alignment // and separating terms using their combinator symbol - for (let i = 0; i < termArray.length; i++) { - const termText = termArray[i]; - const spaceCount = (maxTermLength + 2) - termTextLengths[i]; + for (let i = 0; i < renderedTerms.length; i++) { + const termText = renderedTerms[i].text; + const spaceCount = (maxTermLength + 2) - renderedTerms[i].length; let combinatorText = ''; if (combinator && combinator !== " ") { const info = syntaxDescriptions[combinator]; @@ -206,7 +215,7 @@ function renderTerms(terms, combinator) { combinatorText = `${combinator}`; } // omit the combinator for the final term - combinatorText = (i < termArray.length-1 ? combinatorText : ''); + combinatorText = (i < renderedTerms.length-1 ? combinatorText : ''); output += ` ${termText}${' '.repeat(spaceCount)}${combinatorText}
`; } @@ -265,7 +274,7 @@ function getConstituentTypes(propertySyntax) { while (true) { oldConstituentsLength = allConstituents.length; getTypesForSyntaxes(constituentSyntaxes, allConstituents); - + if (allConstituents.length <= oldConstituentsLength) { break; } From 8f619c5e34f56774cb5ac6b555a2775837720647 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Tue, 21 Jun 2022 12:00:54 -0700 Subject: [PATCH 26/29] Use mdn.localString for human-readable strings --- kumascript/macros/CSSSyntax.ejs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 8fb9779efeb5..40d9cdba19dc 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -21,43 +21,43 @@ const valueDefinitionUrl = `/${locale}/docs/Web/CSS/Value_definition_syntax`; const syntaxDescriptions = { '*': { fragment: 'asterisk', - tooltip: 'Asterisk: the entity may occur zero, one or several times' + tooltip: mdn.localString({ 'en-US': 'Asterisk: the entity may occur zero, one or several times' }) }, '+': { fragment: 'plus', - tooltip: 'Plus: the entity may occur one or several times' + tooltip: mdn.localString({ 'en-US': 'Plus: the entity may occur one or several times' }) }, '?': { fragment: 'question_mark', - tooltip: 'Question mark: the entity is optional' + tooltip: mdn.localString({ 'en-US': 'Question mark: the entity is optional' }) }, '{}': { fragment: 'curly_braces', - tooltip: 'Curly braces: encloses two integers defining the minimal and maximal numbers of occurrences of the entity' + tooltip: mdn.localString({ 'en-US': 'Curly braces: encloses two integers defining the minimal and maximal numbers of occurrences of the entity' }) }, '#': { fragment: 'hash_mark', - tooltip: 'Hash mark: the entity is repeated one or several times, each occurence separated by a comma' + tooltip: mdn.localString({ 'en-US': 'Hash mark: the entity is repeated one or several times, each occurence separated by a comma' }) }, '!': { fragment: 'exclamation_point_!', - tooltip: 'Exclamation point: the group must produce at least one value' + tooltip: mdn.localString({ 'en-US': 'Exclamation point: the group must produce at least one value' }) }, '[]': { fragment: 'brackets', - tooltip: 'Brackets: enclose several entities, combinators, and multipliers to transform them as a single component' + tooltip: mdn.localString({ 'en-US': 'Brackets: enclose several entities, combinators, and multipliers to transform them as a single component' }) }, '|': { fragment: 'single_bar', - tooltip: 'Single bar: exactly one of the entities must be present' + tooltip: mdn.localString({ 'en-US': 'Single bar: exactly one of the entities must be present' }) }, '||': { fragment: 'double_bar', - tooltip: 'Double bar: one or several of the entities must be present, in any order' + tooltip: mdn.localString({ 'en-US': 'Double bar: one or several of the entities must be present, in any order' }) }, '&&': { fragment: 'double_ampersand', - tooltip: 'Double ampersand: all of the entities must be present, in any order' + tooltip: mdn.localString({ 'en-US': 'Double ampersand: all of the entities must be present, in any order' }) } } From 69c852a9562cb5af52ad1f376b6ae96f0bf77511 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 22 Jun 2022 11:31:26 -0700 Subject: [PATCH 27/29] Remove replaceAll polyfill --- kumascript/macros/CSSSyntax.ejs | 12 ++++++------ package.json | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 40d9cdba19dc..7eeaf65e21b8 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -10,7 +10,6 @@ const webRefData = require('@webref/css'); const { definitionSyntax } = require('css-tree'); -const replaceAll = require('string.prototype.replaceall'); const locale = env.locale; @@ -125,8 +124,8 @@ function renderNode(name, node) { } case 'Type': { // encode < and > - let encoded = replaceAll(name, '<', '<'); - encoded = replaceAll(encoded, '>', '>'); + let encoded = name.replaceAll('<', '<'); + encoded = encoded.replaceAll('>', '>'); // add CSS class: we use "property" because there isn't one for types const span = `${encoded}`; // if this type is not included in the syntax, link to its dedicated page @@ -134,8 +133,9 @@ function renderNode(name, node) { return span; } else { // the slug does not include the angle brackets - let slug = replaceAll(name, '<', ''); - slug = replaceAll(slug, '>', ''); + let slug = name.replaceAll('<', ''); + slug = slug.replaceAll('>', ''); + // also remove the range in square brackets, as in "" slug = slug.replace(/\[.*\]/, '') return `${span}`; } @@ -171,7 +171,7 @@ function renderNode(name, node) { if (node.combinator && (node.combinator !== ' ')) { const info = syntaxDescriptions[node.combinator]; // note that we are replacing the combinator surrounded by spaces, like " | " - name = replaceAll(name, ` ${node.combinator} `, ` ${node.combinator} `); + name = name.replaceAll(` ${node.combinator} `, ` ${node.combinator} `); } return name; diff --git a/package.json b/package.json index a952e64eec7c..f94dc3f8e9d4 100644 --- a/package.json +++ b/package.json @@ -224,7 +224,6 @@ "semver": "^7.3.5", "source-map-explorer": "^2.5.2", "source-map-loader": "^3.0.0", - "string.prototype.replaceall": "^1.0.6", "style-dictionary": "^3.7.1", "style-loader": "^3.3.1", "stylelint": "^13.13.1", From 42fca896f6eb0b8420ab3b48ed211479857d0d18 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 22 Jun 2022 17:53:28 -0700 Subject: [PATCH 28/29] Actually concaenate newValues onto values --- kumascript/macros/CSSSyntax.ejs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kumascript/macros/CSSSyntax.ejs b/kumascript/macros/CSSSyntax.ejs index 7eeaf65e21b8..3bfd42d2d2f2 100644 --- a/kumascript/macros/CSSSyntax.ejs +++ b/kumascript/macros/CSSSyntax.ejs @@ -97,7 +97,6 @@ function getPropertySyntax(propertyName) { // 4) If we still have > 1 spec, assume that: // - one of them is the base spec, which defines `values`, // - the others define incremental additions as `newValues` - // Concatenate new values on to values to return a single syntax string let syntax = ''; let newSyntaxes = ''; for (const specName of specsForProp) { @@ -110,6 +109,10 @@ function getPropertySyntax(propertyName) { newSyntaxes += ` | ${newValues}`; } } + // Concatenate new values on to values to return a single syntax string + if (newSyntaxes) { + syntax += newSyntaxes; + } return syntax; } From 09292622af4f7d4b388e4b3642aaee97fbe289f1 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 22 Jun 2022 17:59:27 -0700 Subject: [PATCH 29/29] updated yarn.lock --- yarn.lock | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 413d6ed8bebb..bdbebd5ac0d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4135,6 +4135,14 @@ css-select@^5.1.0: domutils "^3.0.1" nth-check "^2.0.1" +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-tree@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.1.0.tgz#170e27ccf94e7c5facb183765c25898be843d1d2" @@ -8409,7 +8417,7 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== -mdn-data@^2.0.27: +mdn-data@2.0.27, mdn-data@^2.0.27: version "2.0.27" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.27.tgz#1710baa7b0db8176d3b3d565ccb7915fc69525ab" integrity sha512-kwqO0I0jtWr25KcfLm9pia8vLZ8qoAKhWZuZMbneJq3jjBD3gl5nZs8l8Tu3ZBlBAHVQtDur9rdDGyvtfVraHQ== @@ -11473,18 +11481,6 @@ string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.7: regexp.prototype.flags "^1.4.1" side-channel "^1.0.4" -string.prototype.replaceall@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.replaceall/-/string.prototype.replaceall-1.0.6.tgz#566cba7c413713d0b1a85c5dba98b31f8db38196" - integrity sha512-OA8VDhE7ssNFlyoDXUHxw6V5cjgPrtosyJKqJX5i1P5tV9eUynsbhx1yz0g+Ye4fjFwAxhKLxt8GSRx2Aqc+Sw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - get-intrinsic "^1.1.1" - has-symbols "^1.0.2" - is-regex "^1.1.4" - string.prototype.trimend@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0"