|
|
@ -54,6 +54,19 @@ const STRICT = process.argv.includes('--error'); |
|
|
const ROOTS = ['app', 'components', 'src']; |
|
|
const ROOTS = ['app', 'components', 'src']; |
|
|
const TARGET = 8; |
|
|
const TARGET = 8; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 间距令牌镜像(必须与 src/theme/spacing.ts 同步!)。 |
|
|
|
|
|
* 改了 spacing.ts 的取值后,这里不更新会导致审计门禁失准(令牌被当成「无数值」而跳过)。 |
|
|
|
|
|
* 键覆盖标尺(xs/sm/md/lg/xl)与语义令牌(card/gutter/framework/block/section)。 |
|
|
|
|
|
*/ |
|
|
|
|
|
const SPACING_TOKENS = { |
|
|
|
|
|
xs: 4, sm: 8, md: 12, lg: 16, xl: 24, |
|
|
|
|
|
card: 8, gutter: 8, framework: 16, block: 12, section: 12, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** 记录本批命中的令牌引用,供输出复核(证明令牌化后审计仍在工作) */ |
|
|
|
|
|
const tokenizedHits = []; |
|
|
|
|
|
|
|
|
/** 要核对的间距维度 */ |
|
|
/** 要核对的间距维度 */ |
|
|
const CARD_GAP_PROPS = ['marginTop', 'marginBottom']; |
|
|
const CARD_GAP_PROPS = ['marginTop', 'marginBottom']; |
|
|
|
|
|
|
|
|
@ -125,12 +138,34 @@ function readNumber(text, prop) { |
|
|
return m ? Number(m[1]) : null; |
|
|
return m ? Number(m[1]) : null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 读间距值:先试裸数字,再试令牌引用(spacing.x / spacingTokens.x)。 |
|
|
|
|
|
* 这样卡片 / 留白改成 `spacing.card` 这类写法后,审计仍能解析出数值并核对, |
|
|
|
|
|
* 不会因为「读不到数字」而把该项静默跳过(那会撕开审计网)。 |
|
|
|
|
|
* 未知令牌名按 null 处理(交给调用方,至多误判,绝不静默通过)。 |
|
|
|
|
|
*/ |
|
|
|
|
|
function resolveSpacing(text, prop) { |
|
|
|
|
|
const num = readNumber(text, prop); |
|
|
|
|
|
if (num !== null) return num; |
|
|
|
|
|
const tm = flatten(text).match( |
|
|
|
|
|
new RegExp(`^[ \\t]*${prop}:[ \\t]*(?:spacing|spacingTokens)\\.([A-Za-z_$][\\w$]*)`, 'm'), |
|
|
|
|
|
); |
|
|
|
|
|
if (tm) { |
|
|
|
|
|
const v = SPACING_TOKENS[tm[1]]; |
|
|
|
|
|
if (typeof v === 'number') { |
|
|
|
|
|
tokenizedHits.push(`${prop}: ${tm[1]}`); |
|
|
|
|
|
return v; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const hasProp = (text, prop) => new RegExp(`^[ \\t]*${prop}:`, 'm').test(flatten(text)); |
|
|
const hasProp = (text, prop) => new RegExp(`^[ \\t]*${prop}:`, 'm').test(flatten(text)); |
|
|
|
|
|
|
|
|
const isCircle = ({ text }) => { |
|
|
const isCircle = ({ text }) => { |
|
|
const w = readNumber(text, 'width'); |
|
|
const w = resolveSpacing(text, 'width'); |
|
|
const h = readNumber(text, 'height'); |
|
|
const h = resolveSpacing(text, 'height'); |
|
|
const r = readNumber(text, 'borderRadius'); |
|
|
const r = resolveSpacing(text, 'borderRadius'); |
|
|
return w !== null && h !== null && w === h && Math.abs(r - w / 2) < 0.51; |
|
|
return w !== null && h !== null && w === h && Math.abs(r - w / 2) < 0.51; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
@ -147,7 +182,7 @@ for (const file of files) { |
|
|
if (!src.includes('StyleSheet.create(')) continue; |
|
|
if (!src.includes('StyleSheet.create(')) continue; |
|
|
|
|
|
|
|
|
for (const block of styleBlocks(src)) { |
|
|
for (const block of styleBlocks(src)) { |
|
|
if (readNumber(block.text, 'borderRadius') === null) continue; |
|
|
if (resolveSpacing(block.text, 'borderRadius') === null) continue; |
|
|
if (!hasProp(block.text, 'backgroundColor') && !hasProp(block.text, 'borderWidth')) continue; |
|
|
if (!hasProp(block.text, 'backgroundColor') && !hasProp(block.text, 'borderWidth')) continue; |
|
|
if (isCircle(block)) { |
|
|
if (isCircle(block)) { |
|
|
excluded.push(`${file} | ${block.key} | 圆 ${readNumber(block.text, 'width')}`); |
|
|
excluded.push(`${file} | ${block.key} | 圆 ${readNumber(block.text, 'width')}`); |
|
|
@ -159,19 +194,19 @@ for (const file of files) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
cardCount += 1; |
|
|
cardCount += 1; |
|
|
const radius = readNumber(block.text, 'borderRadius'); |
|
|
const radius = resolveSpacing(block.text, 'borderRadius'); |
|
|
if (radius !== TARGET) { |
|
|
if (radius !== TARGET) { |
|
|
violations.radius.push(`${file} | ${block.key} | borderRadius ${radius}`); |
|
|
violations.radius.push(`${file} | ${block.key} | borderRadius ${radius}`); |
|
|
continue; |
|
|
continue; |
|
|
} |
|
|
} |
|
|
radiusOk += 1; |
|
|
radiusOk += 1; |
|
|
|
|
|
|
|
|
const padding = readNumber(block.text, 'padding'); |
|
|
const padding = resolveSpacing(block.text, 'padding'); |
|
|
if (padding !== null) { |
|
|
if (padding !== null) { |
|
|
if (padding !== TARGET) violations.padding.push(`${file} | ${block.key} | padding ${padding}`); |
|
|
if (padding !== TARGET) violations.padding.push(`${file} | ${block.key} | padding ${padding}`); |
|
|
} else { |
|
|
} else { |
|
|
for (const prop of ['paddingHorizontal', 'paddingVertical']) { |
|
|
for (const prop of ['paddingHorizontal', 'paddingVertical']) { |
|
|
const value = readNumber(block.text, prop); |
|
|
const value = resolveSpacing(block.text, prop); |
|
|
if (value !== null && value !== TARGET) { |
|
|
if (value !== null && value !== TARGET) { |
|
|
violations.padding.push(`${file} | ${block.key} | ${prop} ${value}`); |
|
|
violations.padding.push(`${file} | ${block.key} | ${prop} ${value}`); |
|
|
} |
|
|
} |
|
|
@ -179,7 +214,7 @@ for (const file of files) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const prop of CARD_GAP_PROPS) { |
|
|
for (const prop of CARD_GAP_PROPS) { |
|
|
const value = readNumber(block.text, prop); |
|
|
const value = resolveSpacing(block.text, prop); |
|
|
if (value !== null && value !== TARGET) { |
|
|
if (value !== null && value !== TARGET) { |
|
|
violations.gap.push(`${file} | ${block.key} | ${prop} ${value}`); |
|
|
violations.gap.push(`${file} | ${block.key} | ${prop} ${value}`); |
|
|
} |
|
|
} |
|
|
@ -206,8 +241,8 @@ for (const file of files) { |
|
|
for (const key of candidates) { |
|
|
for (const key of candidates) { |
|
|
const block = blocks.find((b) => b.key === key); |
|
|
const block = blocks.find((b) => b.key === key); |
|
|
if (!block) continue; |
|
|
if (!block) continue; |
|
|
const padding = readNumber(block.text, 'padding'); |
|
|
const padding = resolveSpacing(block.text, 'padding'); |
|
|
const horizontal = readNumber(block.text, 'paddingHorizontal'); |
|
|
const horizontal = resolveSpacing(block.text, 'paddingHorizontal'); |
|
|
if (padding === null && horizontal === null) continue; |
|
|
if (padding === null && horizontal === null) continue; |
|
|
const value = padding !== null ? padding : horizontal; |
|
|
const value = padding !== null ? padding : horizontal; |
|
|
if (value !== TARGET) violations.gutter.push(`${file} | ${key} | ${value}`); |
|
|
if (value !== TARGET) violations.gutter.push(`${file} | ${key} | ${value}`); |
|
|
@ -229,6 +264,8 @@ console.log(` 卡片间隔 ≠ ${TARGET}:${violations.gap.length}`); |
|
|
violations.gap.forEach((line) => console.log(` ${line}`)); |
|
|
violations.gap.forEach((line) => console.log(` ${line}`)); |
|
|
console.log(` 页面留白 ≠ ${TARGET}:${violations.gutter.length}`); |
|
|
console.log(` 页面留白 ≠ ${TARGET}:${violations.gutter.length}`); |
|
|
violations.gutter.forEach((line) => console.log(` ${line}`)); |
|
|
violations.gutter.forEach((line) => console.log(` ${line}`)); |
|
|
|
|
|
console.log(`--- 令牌引用命中 ${tokenizedHits.length} 处(spacing.* / spacingTokens.*,审计已解析) ---`); |
|
|
|
|
|
tokenizedHits.forEach((line) => console.log(` ${line}`)); |
|
|
console.log(`--- 已剔除 ${excluded.length} 项(圆 / 控件),逐条列出供复核 ---`); |
|
|
console.log(`--- 已剔除 ${excluded.length} 项(圆 / 控件),逐条列出供复核 ---`); |
|
|
excluded.forEach((line) => console.log(` ${line}`)); |
|
|
excluded.forEach((line) => console.log(` ${line}`)); |
|
|
|
|
|
|
|
|
|