diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 929d30156..5fabe203b 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -12,6 +12,7 @@ import { normalize } from '../util/dataTypes' import { isValidVariantFormatString, parseVariant } from './setupContextUtils' import isValidArbitraryValue from '../util/isValidArbitraryValue' import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js' +import { isParsableNode, isParsableCssValue, isValidPropName } from '../util/css-validation.js' let classNameParser = selectorParser((selectors) => { return selectors.first.filter(({ type }) => type === 'class').pop().value @@ -303,61 +304,6 @@ function parseRules(rule, cache, options = {}) { return [cache.get(rule), options] } -const IS_VALID_PROPERTY_NAME = /^[a-z_-]/ - -function isValidPropName(name) { - return IS_VALID_PROPERTY_NAME.test(name) -} - -/** - * @param {string} declaration - * @returns {boolean} - */ -function looksLikeUri(declaration) { - // Quick bailout for obvious non-urls - // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem - if (!declaration.includes('://')) { - return false - } - - try { - const url = new URL(declaration) - return url.scheme !== '' && url.host !== '' - } catch (err) { - // Definitely not a valid url - return false - } -} - -function isParsableNode(node) { - let isParsable = true - - node.walkDecls((decl) => { - if (!isParsableCssValue(decl.name, decl.value)) { - isParsable = false - return false - } - }) - - return isParsable -} - -function isParsableCssValue(property, value) { - // We don't want to to treat [https://example.com] as a custom property - // Even though, according to the CSS grammar, it's a totally valid CSS declaration - // So we short-circuit here by checking if the custom property looks like a url - if (looksLikeUri(`${property}:${value}`)) { - return false - } - - try { - postcss.parse(`a{${property}:${value}}`).toResult() - return true - } catch (err) { - return false - } -} - function extractArbitraryProperty(classCandidate, context) { let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? [] diff --git a/src/util/css-validation.js b/src/util/css-validation.js new file mode 100644 index 000000000..11f814e8d --- /dev/null +++ b/src/util/css-validation.js @@ -0,0 +1,56 @@ +import postcss from 'postcss' + +const IS_VALID_PROPERTY_NAME = /^[a-z_-]/ + +export function isValidPropName(name) { + return IS_VALID_PROPERTY_NAME.test(name) +} + +/** + * @param {string} declaration + * @returns {boolean} + */ +export function looksLikeUri(declaration) { + // Quick bailout for obvious non-urls + // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem + if (!declaration.includes('://')) { + return false + } + + try { + const url = new URL(declaration) + return url.scheme !== '' && url.host !== '' + } catch (err) { + // Definitely not a valid url + return false + } +} + +export function isParsableNode(node) { + let isParsable = true + + node.walkDecls((decl) => { + if (!isParsableCssValue(decl.name, decl.value)) { + isParsable = false + return false + } + }) + + return isParsable +} + +export function isParsableCssValue(property, value) { + // We don't want to to treat [https://example.com] as a custom property + // Even though, according to the CSS grammar, it's a totally valid CSS declaration + // So we short-circuit here by checking if the custom property looks like a url + if (looksLikeUri(`${property}:${value}`)) { + return false + } + + try { + postcss.parse(`a{${property}:${value}}`).toResult() + return true + } catch (err) { + return false + } +}