"use strict" const declarationValueIndex = require("../../utils/declarationValueIndex") const isNumbery = require("../../utils/isNumbery") const isStandardSyntaxValue = require("../../utils/isStandardSyntaxValue") const isVariable = require("../../utils/isVariable") const optionsMatches = require("../../utils/optionsMatches") const report = require("../../utils/report") const ruleMessages = require("../../utils/ruleMessages") const validateOptions = require("../../utils/validateOptions") const keywordSets = require("../../reference/keywordSets") const _ = require("lodash") const postcss = require("postcss") const ruleName = "font-weight-notation" const messages = ruleMessages(ruleName, { expected: type => `Expected ${type} font-weight notation`, invalidNamed: name => `Unexpected invalid font-weight name "${name}"`, }) const INHERIT_KEYWORD = "inherit" const INITIAL_KEYWORD = "initial" const NORMAL_KEYWORD = "normal" const WEIGHTS_WITH_KEYWORD_EQUIVALENTS = [ "400", "700", ] const rule = function (expectation, options) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: [ "numeric", "named-where-possible", ], }, { actual: options, possible: { ignore: ["relative"], }, optional: true, }) if (!validOptions) { return } root.walkDecls(decl => { if (decl.prop.toLowerCase() === "font-weight") { checkWeight(decl.value, decl) } if (decl.prop.toLowerCase() === "font") { checkFont(decl) } }) function checkFont(decl) { const valueList = postcss.list.space(decl.value) // We do not need to more carefully distinguish font-weight // numbers from unitless line-heights because line-heights in // `font` values need to be part of a font-size/line-height pair const hasNumericFontWeight = valueList.some(isNumbery) for (const value of postcss.list.space(decl.value)) { if (value.toLowerCase() === NORMAL_KEYWORD && !hasNumericFontWeight || isNumbery(value) || value.toLowerCase() !== NORMAL_KEYWORD && keywordSets.fontWeightKeywords.has(value.toLowerCase())) { checkWeight(value, decl) return } } } function checkWeight(weightValue, decl) { if (!isStandardSyntaxValue(weightValue)) { return } if (isVariable(weightValue)) { return } if (weightValue.toLowerCase() === INHERIT_KEYWORD || weightValue.toLowerCase() === INITIAL_KEYWORD) { return } if (optionsMatches(options, "ignore", "relative") && keywordSets.fontWeightRelativeKeywords.has(weightValue.toLowerCase())) { return } const weightValueOffset = decl.value.indexOf(weightValue) if (expectation === "numeric") { if (!isNumbery(weightValue)) { return complain(messages.expected("numeric")) } } if (expectation === "named-where-possible") { if (isNumbery(weightValue)) { if (_.includes(WEIGHTS_WITH_KEYWORD_EQUIVALENTS, weightValue)) { complain(messages.expected("named")) } return } if (!keywordSets.fontWeightKeywords.has(weightValue.toLowerCase()) && weightValue.toLowerCase() !== NORMAL_KEYWORD) { return complain(messages.invalidNamed(weightValue)) } return } function complain(message) { report({ ruleName, result, message, node: decl, index: declarationValueIndex(decl) + weightValueOffset, }) } } } } rule.ruleName = ruleName rule.messages = messages module.exports = rule