stylelint function-allowed-list 规则详解:用白名单约束 CSS 函数的使用
2026/9/23 14:32:18 网站建设 项目流程
  • 代码质量
  • 静态分析
  • 前端

【免费下载链接】stylelint

A mighty CSS linter that helps you avoid errors and enforce conventions.

项目地址:https://gitcode.com/gh_mirrors/st/stylelint
点击查看免费下载

导读

function-allowed-list是 stylelint 内置的一条白名单型规则,用于限定样式表中允许出现的 CSS 函数(如scale()rgba()linear-gradient()),凡是未列入名单的函数都会被报告为违规。这条规则非常适合规范团队在transformcolorbackground等声明中使用的函数集合,例如强制统一颜色函数、限制渐变写法、禁止某类不兼容函数。读完本文,你将掌握该规则的完整配置语法(字符串与正则混合)、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/"能同时命中rgbrgba
  • 原生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); }

rotatehsla不在名单中,-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),判定流程是:

  1. 函数命中主名单matchesStringOrRegExp(funcName, primary))才进入下一步,否则直接报错;
  2. 函数未命中exceptWithoutPropertyFallback名单 → 放行;
  3. 函数命中回退名单,且hasPrevPropertyDeclaration(decl)返回true(前面已有同属性声明)→ 放行;
  4. 其余情况 → 报错。

关键在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),核心流程如下:

  1. 快速剪枝if (!decl.value.includes('(')) return;——声明值中不含左括号就直接跳过,避免对绝大多数普通声明做无谓解析。
  2. 解析值:用postcss-value-parser解析声明值,逐个访问节点。
  3. 函数节点判定:通过isValueFunction(node)判断是否为函数节点;再通过 lib/utils/isStandardSyntaxFunction.mjs 排除非标准语法——源码注释明确说明:无名的括号内容(如 Sass 列表)、#{...}(SCSS 插值)、${...}`...`(CSS-in-JS 插值)都不视为标准函数,因此不会误报。
  4. 匹配与报告:按上文的两层过滤逻辑判定;需要报错时,用declarationValueIndex(decl) + sourceIndex计算函数在源码中的起始位置(lib/utils/nodeFieldIndices.mjs 负责计算声明值的起始索引),并用index/endIndex精确定位违规区间,再通过统一的report工具上报。

测试用例对位置精度有严格要求,例如a { transform: rOtAtE(7deg) }报错于第 1 行第 16 列至 22 列(index.mjs#L33-L39),并且验证了大小写变体(rOtAtEROTATEsCaLeSCALE)会以原文大小写出现在错误消息中——因为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-listfunction-url-quotes:针对url()场景的专项规则,可组合使用。

小结

function-allowed-list是一个配置简单、行为精确的函数白名单规则:主选项支持字符串与正则混合,匹配区分大小写(除非正则显式使用/i);exceptWithoutPropertyFallback通过"同一声明块内、同属性名、位置靠前"三条规则实现回退检测,并对自定义属性与跨规则场景做了严格排除。理解 matchesStringOrRegExp.mjs 的匹配语义和 hasPrevPropertyDeclaration 的方向性查找逻辑,就能准确预判任何一条样式会不会被这条规则拦下。

  • 代码质量
  • 静态分析
  • 前端

【免费下载链接】stylelint

A mighty CSS linter that helps you avoid errors and enforce conventions.

项目地址:https://gitcode.com/gh_mirrors/st/stylelint
点击查看免费下载

相关推荐

上一篇:the-super-tiny-compiler 项目常见问题解决方案
下一篇:Open-XML-SDK验证系统详解:确保Office文档合规性的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询