- 代码质量
- 静态分析
- 前端
【免费下载链接】stylelint
A mighty CSS linter that helps you avoid errors and enforce conventions.
导读
function-allowed-list是 stylelint 内置的一条白名单型规则,用于限定样式表中允许出现的 CSS 函数(如scale()、rgba()、linear-gradient()),凡是未列入名单的函数都会被报告为违规。这条规则非常适合规范团队在transform、color、background等声明中使用的函数集合,例如强制统一颜色函数、限制渐变写法、禁止某类不兼容函数。读完本文,你将掌握该规则的完整配置语法(字符串与正则混合)、exceptWithoutPropertyFallback回退检测机制的精确语义,以及它背后的源码实现原理与测试覆盖。
规则概述:Specify a list of allowed functions
规则的核心功能正如其描述:指定一个允许使用的函数列表(Specify a list of allowed functions)。凡是出现在声明值中、但不在允许名单内的函数,都会被标记为问题。
以最典型的transform声明为例:
a { transform: scale(1); } /** ↑ * This function */上图中箭头指向的scale(1)就是被检查的目标。规则只关心函数调用本身(即带有(的标识符),普通的属性值、颜色关键字、长度单位等都不受影响。
规则在 lib/rules/index.mjs 中注册,实现代码位于 lib/rules/function-allowed-list/index.mjs,与之互补的对称规则是function-disallowed-list(黑名单),两者在配置上结构一致、语义相反。
主选项:Array<string>(字符串与正则混合)
主选项是一个字符串数组,数组中的每一项可以是函数名字符串,也可以是以/包裹的正则表达式:
["array", "of", "functions", "/regex/"]从源码看(index.mjs),主选项通过validateOptions校验,要求每个元素为字符串或正则表达式;同时规则声明了rule.primaryOptionArray = true(index.mjs),表示主选项必须以数组形式给出。
字符串与正则的匹配语义
函数名到底如何与名单匹配?这由通用工具 lib/utils/matchesStringOrRegExp.mjs 决定,规则与 stylelint 中大量"列表型"规则共用该逻辑:
- 纯字符串:执行严格相等比较(
value === comparison),区分大小写。因此配置了"scale"后,scale(1)通过,而Scale(1)、SCALE(1)都会被视为未命中名单而报错。 /regex/形式的字符串:以/开头并以/(或/i)结尾的字符串会被转换为RegExp再执行test。正则默认区分大小写;如果以/i结尾,则会以i(忽略大小写)标志创建正则。例如"/rgb/"能同时命中rgb与rgba。- 原生
RegExp:在 JSON 配置中无法书写,但在使用 JS/ESM 格式的配置文件(如stylelint.config.mjs)时可以直接传入,例如[/rgb/]。
测试用例对上述三种形式都有覆盖(lib/rules/function-allowed-list/tests/index.mjs):config: '/rgb/'时rgb()、rgba()被接受而hsl()被拒绝;config: [/rgb/]的原生正则行为完全一致。
配置示例
{ "function-allowed-list": ["scale", "rgba", "/^(-moz-)?linear-gradient$/"] }名单含义:
| 名单项 | 匹配目标 | 说明 |
|---|---|---|
"scale" | scale(...) | 精确匹配,大小写敏感 |
"rgba" | rgba(...) | 精确匹配 |
/^(-moz-)?linear-gradient$/ | linear-gradient(...)、-moz-linear-gradient(...) | 正则完整锚定函数名 |
视为问题(不匹配名单)
a { transform: rotate(1); }a { color: hsla(170, 50%, 45%, 1) }a { background: red, -webkit-radial-gradient(red, green, blue); }rotate、hsla不在名单中,-webkit-radial-gradient也无法被^(-moz-)?linear-gradient$匹配,因此全部报错。
不视为问题(命中名单或与函数无关)
a { background: red; }(red是颜色关键字,不是函数调用)
a { transform: scale(1); }a { color: rgba(0, 0, 0, 0.5); }a { background: red, -moz-linear-gradient(45deg, blue, red); }二级选项:exceptWithoutPropertyFallback
这是该规则最独特的功能:当名单中的函数在同一个声明块内没有对应的"属性回退声明"时,禁止使用这些函数。它专门服务于渐进增强场景——用min()、max()、clamp()这类新函数时,通常要求前面先写一条等价的普通属性声明作为老浏览器回退。
配置形式:
{ "exceptWithoutPropertyFallback": ["array", "of", "functions", "/regex/"] }该选项是一个独立于主选项的二级选项对象,值同样可以是字符串与正则的混合数组(源码见 index.mjs,通过validateOptions校验为[isString, isRegExp])。
判定逻辑:两层过滤
结合源码(index.mjs),判定流程是:
- 函数命中主名单(
matchesStringOrRegExp(funcName, primary))才进入下一步,否则直接报错; - 函数未命中
exceptWithoutPropertyFallback名单 → 放行; - 函数命中回退名单,且
hasPrevPropertyDeclaration(decl)返回true(前面已有同属性声明)→ 放行; - 其余情况 → 报错。
关键在hasPrevPropertyDeclaration(index.mjs)的实现细节:
- 将当前声明的属性名转为小写(
decl.prop.toLowerCase())后,从当前声明向前遍历同一个声明块内的兄弟节点; - 只要找到同属性名的声明就返回
true;与属性值的具体形式无关——即使前面的声明值不含该函数,也算提供了回退; - 自定义属性(
--foo)被排除:isCustomProperty(prop)为真时直接返回false(lib/utils/isCustomProperty.mjs 中仅判断property.startsWith('--')),也就是说--foo前面即使有--foo声明,也不被视为回退; - 遍历只发生在同一声明块内,跨规则(如
a { ... } b { ... })或跨:root的声明不会被算作回退。
配置示例
{ "function-allowed-list": [ ["scale", "min", "/max/"], { "exceptWithoutPropertyFallback": ["min", "/max/"] } ] }注意此时主选项以嵌套数组形式出现:外层第一个元素是主名单["scale", "min", "/max/"],第二个元素是二级选项对象。这是 stylelint 中"数组型主选项 + 二级选项"的标准写法。
视为问题(缺少回退)
a { width: min(50%, 100px); }a { height: max(50%, 100px); }a { width: max(50%, 100px); width: 100px; }第三条最值得注意:max()虽然"看起来"有同属性声明,但回退声明写在了max()之后。hasPrevPropertyDeclaration只向前查找,前面的width: 100px出现在max()之后,不构成回退,因此依然报错。测试用例 index.mjs#L256-L262 专门验证了"回退必须在前"这一方向性。
不视为问题(存在有效回退)
a { transform: scale(1); }(scale不在exceptWithoutPropertyFallback名单中)
a { width: 100px; width: min(50%, 100px); }(min()之前已有同属性width: 100px回退)
a { width: 10px; height: 10px; width: min(50%, 10px); }(测试用例 index.mjs#L233-L235 验证:回退声明与函数声明之间可以插入其他声明,只要同属性声明在本块内更靠前即可)
源码实现:从声明值解析到报告定位
规则的执行入口是root.walkDecls(index.mjs),核心流程如下:
- 快速剪枝:
if (!decl.value.includes('(')) return;——声明值中不含左括号就直接跳过,避免对绝大多数普通声明做无谓解析。 - 解析值:用
postcss-value-parser解析声明值,逐个访问节点。 - 函数节点判定:通过
isValueFunction(node)判断是否为函数节点;再通过 lib/utils/isStandardSyntaxFunction.mjs 排除非标准语法——源码注释明确说明:无名的括号内容(如 Sass 列表)、#{...}(SCSS 插值)、${...}与`...`(CSS-in-JS 插值)都不视为标准函数,因此不会误报。 - 匹配与报告:按上文的两层过滤逻辑判定;需要报错时,用
declarationValueIndex(decl) + sourceIndex计算函数在源码中的起始位置(lib/utils/nodeFieldIndices.mjs 负责计算声明值的起始索引),并用index/endIndex精确定位违规区间,再通过统一的report工具上报。
测试用例对位置精度有严格要求,例如a { transform: rOtAtE(7deg) }报错于第 1 行第 16 列至 22 列(index.mjs#L33-L39),并且验证了大小写变体(rOtAtE、ROTATE、sCaLe、SCALE)会以原文大小写出现在错误消息中——因为messages.rejected的消息参数直接传入的是源码中的函数名(index.mjs#L15-L17)。
配置消息:message二级选项与消息参数
该规则支持 1 个消息参数:被禁止的函数名。根据 docs/user-guide/configure.md 的说明,可以使用message二级选项自定义报错文案,并用"%s"占位符接收函数名,也可以直接在 JS 配置中使用函数形式:
{ "function-allowed-list": ["scale", "rgba", "/^(-moz-)?linear-gradient$/"], "message": "Function \"%s\" is not in the allowed list" }默认消息为Disallowed function "xxx",其中xxx是实际出现在源码中的函数名原文。
与其他规则的配合
function-disallowed-list:黑名单版,与白名单二选一使用,切勿同时配置同一条规则的正反两个版本;function-no-unknown:检查未知函数,与白名单相比更关注"是否存在",而非"是否被允许";function-url-scheme-allowed-list、function-url-quotes:针对url()场景的专项规则,可组合使用。
小结
function-allowed-list是一个配置简单、行为精确的函数白名单规则:主选项支持字符串与正则混合,匹配区分大小写(除非正则显式使用/i);exceptWithoutPropertyFallback通过"同一声明块内、同属性名、位置靠前"三条规则实现回退检测,并对自定义属性与跨规则场景做了严格排除。理解 matchesStringOrRegExp.mjs 的匹配语义和 hasPrevPropertyDeclaration 的方向性查找逻辑,就能准确预判任何一条样式会不会被这条规则拦下。
- 代码质量
- 静态分析
- 前端
【免费下载链接】stylelint
A mighty CSS linter that helps you avoid errors and enforce conventions.
相关推荐
终极指南:5分钟掌握Mac触控板三指中键点击功能
终极指南:5分钟掌握Mac触控板三指中键点击功能 你是否曾羡慕Windows用户轻松使用鼠标中键关闭标签页,而Mac用户只能依赖复杂的快捷键组合?MiddleC
代码质量静态分析前端React与第三方库集成:如何在现有项目中优雅引入React
React与第三方库集成:如何在现有项目中优雅引入React React作为当下最流行的前端框架之一,以其组件化思想和高效的DOM渲染机制受到广大开发者青睐。很
代码质量静态分析前端stylelint media-feature-name-allowed-list 规则详解:用白名单约束媒体特性名称
stylelint media feature name allowed list 规则详解:用白名单约束媒体特性名称 media feature name a
代码质量静态分析前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考