71 lines
3.0 KiB
JavaScript
71 lines
3.0 KiB
JavaScript
// 契约:自定义组件的 WXSS 不得使用标签 / ID / 属性选择器
|
||||
|
|
//
|
|||
|
|
// 由来(2026-09-19)开发者工具警告:
|
|||
|
|
// [pages/index/index] Some selectors are not allowed in component wxss,
|
|||
|
|
// including tag name selectors, ID selectors, and attribute selectors.
|
|||
|
|
// (./components/recommend-card/index.wxss:1:1092)
|
|||
|
|
// 实测违规 1 处:`.compact-like image`。
|
|||
|
|
//
|
|||
|
|
// ⚠️ 作用域刻意限定 `miniapp/components/`:**page 的 wxss 允许标签选择器**,
|
|||
|
|
// 把本规则套到 pages 上会立刻产生误报 —— 守卫一旦有假阳性就会被绕过。
|
|||
|
|
//
|
|||
|
|
// 自检:内置正向样例(合法类选择器 + 伪类 + `>` 子选择器)与反向样例
|
|||
|
|
// (标签 / ID / 属性各一),确保判据既不全哑、也不误伤。
|
|||
|
|
|
|||
|
|
import assert from 'node:assert/strict'
|
|||
|
|
import { readFileSync } from 'node:fs'
|
|||
|
|
import { relative, resolve } from 'node:path'
|
|||
|
|
import { checkWxss, walkWxss } from '../scripts/lib/wxss-selectors.mjs'
|
|||
|
|
|
|||
|
|
const root = resolve(import.meta.dirname, '..')
|
|||
|
|
// WXSS_SCAN_ROOT 仅用于负向注入自证(指向临时目录),默认 miniapp/components
|
|||
|
|
const scanRoot = resolve(root, process.env.WXSS_SCAN_ROOT ?? 'miniapp/components')
|
|||
|
|
|
|||
|
|
// ── 1. 判据自检 ──────────────────────────────────────────────────
|
|||
|
|
const selfCheckCases = [
|
|||
|
|
{
|
|||
|
|
name: '正向:纯类选择器 / 伪类 / 子选择器 均合法',
|
|||
|
|
src: [
|
|||
|
|
'.a { color: red }',
|
|||
|
|
'.a .b { color: red }',
|
|||
|
|
'.a > .b { color: red }',
|
|||
|
|
'.a:active, .b::after { color: red }',
|
|||
|
|
'.a:nth-child(2) { color: red }',
|
|||
|
|
'@media (min-width: 600px) { .a { color: red } }',
|
|||
|
|
'/* .commented-out image { color: red } */',
|
|||
|
|
].join('\n'),
|
|||
|
|
expect: 0,
|
|||
|
|
},
|
|||
|
|
{ name: '反向:标签选择器', src: '.a image { width: 1px }', expect: 1 },
|
|||
|
|
{ name: '反向:ID 选择器', src: '#main { width: 1px }', expect: 1 },
|
|||
|
|
{ name: '反向:属性选择器', src: '.a[data-x="1"] { width: 1px }', expect: 1 },
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for (const c of selfCheckCases) {
|
|||
|
|
const got = checkWxss(c.src)
|
|||
|
|
assert.equal(
|
|||
|
|
got.length,
|
|||
|
|
c.expect,
|
|||
|
|
`判据自检失败 —— ${c.name}\n期望 ${c.expect} 条,实得 ${got.length} 条:\n${got.map((x) => x.message).join('\n')}`,
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── 2. 全量扫描组件 WXSS ────────────────────────────────────────
|
|||
|
|
const files = walkWxss(scanRoot)
|
|||
|
|
assert.ok(files.length > 0, `未在 ${scanRoot} 下找到任何 .wxss —— 扫描清单可能失配,拒绝报绿`)
|
|||
|
|
|
|||
|
|
const broken = []
|
|||
|
|
for (const file of files) {
|
|||
|
|
for (const p of checkWxss(readFileSync(file, 'utf8'))) {
|
|||
|
|
broken.push(`${relative(root, file).replace(/\\/g, '/')}:${p.line} ${p.message}`)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
assert.deepEqual(
|
|||
|
|
broken,
|
|||
|
|
[],
|
|||
|
|
`以下组件 WXSS 使用了不允许的选择器(开发者工具会警告,样式不生效):\n${broken.join('\n')}`,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
console.log(`WXSS component selector contracts passed(${files.length} 个组件样式文件)`)
|