重组前端功能目录并统一代码规范工具链 (#10)
Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
@@ -5,112 +5,118 @@ const projectRoot = new URL('..', import.meta.url)
|
||||
const sourceRoot = new URL('../src', import.meta.url)
|
||||
|
||||
const deprecatedPatterns = [
|
||||
{
|
||||
pattern: /\b(?:bg|border|ring|text)-(?:gray|indigo|purple|violet)-\d+\b/g,
|
||||
message: '使用项目约定的 slate/sky/自然绿色语义色,不要重新引入灰色或紫色系。',
|
||||
},
|
||||
{
|
||||
pattern: /\b(?:bg|border|ring|text)-(?:green|lime)-\d+\b/g,
|
||||
message: '绿色必须使用项目重映射后的 teal 品牌色或 emerald 成功色,不要引入默认 green/lime 荧光色。',
|
||||
},
|
||||
{
|
||||
pattern: /#(?:f0fdfa|ccfbf1|99f6e4|5eead4|2dd4bf|14b8a6|0d9488|0f766e|115e59|134e4a)\b/gi,
|
||||
message: '检测到旧版高饱和 teal 色值,请改用自然绿色设计令牌。',
|
||||
},
|
||||
{
|
||||
pattern: /#(?:c084fc|a855f7|9333ea|7e22ce)\b/gi,
|
||||
message: '检测到禁用的紫色硬编码,请改用 rose 或项目语义令牌。',
|
||||
},
|
||||
{
|
||||
pattern: /#(?:1f2937|9ca3af)\b/gi,
|
||||
message: '检测到默认 gray 调色板硬编码,请改用对应的 slate 色值或语义令牌。',
|
||||
},
|
||||
{
|
||||
pattern: /#60a5fa\b/gi,
|
||||
message: '检测到默认 blue-400 硬编码,请按用途改用 sky 或 amber 等项目语义色。',
|
||||
},
|
||||
{
|
||||
pattern: /text-xs uppercase tracking-\[0\.18em\] text-slate-500/g,
|
||||
message: '重复的小节标签应使用 oc-section-label。',
|
||||
vueOnly: true,
|
||||
},
|
||||
{
|
||||
pattern: /border border-slate-200 bg-slate-50 p-4/g,
|
||||
message: '标准内嵌信息面板应使用 oc-inset-panel。',
|
||||
vueOnly: true,
|
||||
},
|
||||
{
|
||||
pattern: /border-b border-sky-100 bg-\[linear-gradient\(180deg,#e0f2fe_0%,#f8fafc_100%\)\]/g,
|
||||
message: '标准页面头部应使用 oc-page-hero。',
|
||||
vueOnly: true,
|
||||
},
|
||||
{
|
||||
pattern: /\b(?:bg|border|ring|text)-(?:gray|indigo|purple|violet)-\d+\b/g,
|
||||
message: '使用项目约定的 slate/sky/自然绿色语义色,不要重新引入灰色或紫色系。',
|
||||
},
|
||||
{
|
||||
pattern: /\b(?:bg|border|ring|text)-(?:green|lime)-\d+\b/g,
|
||||
message:
|
||||
'绿色必须使用项目重映射后的 teal 品牌色或 emerald 成功色,不要引入默认 green/lime 荧光色。',
|
||||
},
|
||||
{
|
||||
pattern: /#(?:f0fdfa|ccfbf1|99f6e4|5eead4|2dd4bf|14b8a6|0d9488|0f766e|115e59|134e4a)\b/gi,
|
||||
message: '检测到旧版高饱和 teal 色值,请改用自然绿色设计令牌。',
|
||||
},
|
||||
{
|
||||
pattern: /#(?:c084fc|a855f7|9333ea|7e22ce)\b/gi,
|
||||
message: '检测到禁用的紫色硬编码,请改用 rose 或项目语义令牌。',
|
||||
},
|
||||
{
|
||||
pattern: /#(?:1f2937|9ca3af)\b/gi,
|
||||
message: '检测到默认 gray 调色板硬编码,请改用对应的 slate 色值或语义令牌。',
|
||||
},
|
||||
{
|
||||
pattern: /#60a5fa\b/gi,
|
||||
message: '检测到默认 blue-400 硬编码,请按用途改用 sky 或 amber 等项目语义色。',
|
||||
},
|
||||
{
|
||||
pattern: /text-xs uppercase tracking-\[0\.18em\] text-slate-500/g,
|
||||
message: '重复的小节标签应使用 oc-section-label。',
|
||||
vueOnly: true,
|
||||
},
|
||||
{
|
||||
pattern: /border border-slate-200 bg-slate-50 p-4/g,
|
||||
message: '标准内嵌信息面板应使用 oc-inset-panel。',
|
||||
vueOnly: true,
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
/border-b border-sky-100 bg-\[linear-gradient\(180deg,#e0f2fe_0%,#f8fafc_100%\)\]/g,
|
||||
message: '标准页面头部应使用 oc-page-hero。',
|
||||
vueOnly: true,
|
||||
},
|
||||
]
|
||||
|
||||
const styleSourceExtensions = new Set(['.vue', '.css', '.ts'])
|
||||
|
||||
async function collectStyleSourceFiles(directory) {
|
||||
const entries = await readdir(directory, { withFileTypes: true })
|
||||
const files = await Promise.all(entries.map(async (entry) => {
|
||||
const path = join(directory, entry.name)
|
||||
return entry.isDirectory() ? collectStyleSourceFiles(path) : [path]
|
||||
}))
|
||||
const entries = await readdir(directory, { withFileTypes: true })
|
||||
const files = await Promise.all(
|
||||
entries.map(async entry => {
|
||||
const path = join(directory, entry.name)
|
||||
return entry.isDirectory() ? collectStyleSourceFiles(path) : [path]
|
||||
}),
|
||||
)
|
||||
|
||||
return files.flat().filter((path) => styleSourceExtensions.has(extname(path)))
|
||||
return files.flat().filter(path => styleSourceExtensions.has(extname(path)))
|
||||
}
|
||||
|
||||
function lineNumberAt(source, index) {
|
||||
return source.slice(0, index).split('\n').length
|
||||
return source.slice(0, index).split('\n').length
|
||||
}
|
||||
|
||||
const violations = []
|
||||
|
||||
for (const file of await collectStyleSourceFiles(sourceRoot.pathname)) {
|
||||
const source = await readFile(file, 'utf8')
|
||||
const source = await readFile(file, 'utf8')
|
||||
|
||||
for (const rule of deprecatedPatterns) {
|
||||
if (rule.vueOnly && extname(file) !== '.vue') continue
|
||||
for (const rule of deprecatedPatterns) {
|
||||
if (rule.vueOnly && extname(file) !== '.vue') continue
|
||||
|
||||
for (const match of source.matchAll(rule.pattern)) {
|
||||
violations.push({
|
||||
file: relative(projectRoot.pathname, file),
|
||||
line: lineNumberAt(source, match.index),
|
||||
message: rule.message,
|
||||
value: match[0],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const match of source.matchAll(/(?:^|\s)class="([^"]+)"/gm)) {
|
||||
const classes = match[1].split(/\s+/)
|
||||
const panelModifier = classes.some((name) => name.startsWith('oc-panel-button--'))
|
||||
const primaryModifier = classes.some((name) => name.startsWith('oc-primary-button--'))
|
||||
|
||||
if (panelModifier && !classes.includes('oc-panel-button')) {
|
||||
violations.push({
|
||||
file: relative(projectRoot.pathname, file),
|
||||
line: lineNumberAt(source, match.index),
|
||||
message: 'oc-panel-button 修饰类必须与基础类一起使用。',
|
||||
value: match[0],
|
||||
})
|
||||
for (const match of source.matchAll(rule.pattern)) {
|
||||
violations.push({
|
||||
file: relative(projectRoot.pathname, file),
|
||||
line: lineNumberAt(source, match.index),
|
||||
message: rule.message,
|
||||
value: match[0],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (primaryModifier && !classes.includes('oc-primary-button')) {
|
||||
violations.push({
|
||||
file: relative(projectRoot.pathname, file),
|
||||
line: lineNumberAt(source, match.index),
|
||||
message: 'oc-primary-button 修饰类必须与基础类一起使用。',
|
||||
value: match[0],
|
||||
})
|
||||
for (const match of source.matchAll(/(?:^|\s)class="([^"]+)"/gm)) {
|
||||
const classes = match[1].split(/\s+/)
|
||||
const panelModifier = classes.some(name => name.startsWith('oc-panel-button--'))
|
||||
const primaryModifier = classes.some(name => name.startsWith('oc-primary-button--'))
|
||||
|
||||
if (panelModifier && !classes.includes('oc-panel-button')) {
|
||||
violations.push({
|
||||
file: relative(projectRoot.pathname, file),
|
||||
line: lineNumberAt(source, match.index),
|
||||
message: 'oc-panel-button 修饰类必须与基础类一起使用。',
|
||||
value: match[0],
|
||||
})
|
||||
}
|
||||
|
||||
if (primaryModifier && !classes.includes('oc-primary-button')) {
|
||||
violations.push({
|
||||
file: relative(projectRoot.pathname, file),
|
||||
line: lineNumberAt(source, match.index),
|
||||
message: 'oc-primary-button 修饰类必须与基础类一起使用。',
|
||||
value: match[0],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error('样式体系检查失败:')
|
||||
for (const violation of violations) {
|
||||
console.error(`- ${violation.file}:${violation.line} ${violation.message} (${violation.value})`)
|
||||
}
|
||||
process.exitCode = 1
|
||||
console.error('样式体系检查失败:')
|
||||
for (const violation of violations) {
|
||||
console.error(
|
||||
`- ${violation.file}:${violation.line} ${violation.message} (${violation.value})`,
|
||||
)
|
||||
}
|
||||
process.exitCode = 1
|
||||
} else {
|
||||
console.log('样式体系检查通过。')
|
||||
console.log('样式体系检查通过。')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user