mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
Fail the lint on css that reads an undefined custom property (#2926)
This commit is contained in:
1 parent
e1ecd39a3c
commit
2662dcd0ee
3 files changed
+158
-1
No files matched your search
@@ -0,0 +1,87 @@
|
||||
// Fails when the css references a custom property that nothing defines.
|
||||
//
|
||||
// `color: var(--tblr-nav-link-active-color)` where that name is never declared
|
||||
// is invalid at computed-value time: the browser drops the whole declaration and
|
||||
// the element falls back to the inherited or initial value. Nothing reports it —
|
||||
// not sass, not stylelint, not the browser console — so it renders wrong and
|
||||
// quietly. Three shipped bugs came from exactly this: the button focus ring
|
||||
// (#2662), the active nav link (#2287) and the disabled button border.
|
||||
//
|
||||
// A `var()` with a fallback cannot break, so those references are not checked.
|
||||
//
|
||||
// The scan compiles the same scss entry points the build does, so it needs no
|
||||
// dist/ and sees what actually ships. Names that stay unprefixed belong to
|
||||
// third-party stylesheets that declare them themselves (see cssVarIgnore), so
|
||||
// only `--tblr-` names are checked.
|
||||
//
|
||||
// Known offenders are listed in css-vars-baseline.txt. Anything not on that list
|
||||
// fails the check; so does a name on the list that no longer dangles, which
|
||||
// keeps the file from going stale — delete the line when you fix one.
|
||||
//
|
||||
// Usage: tsx .build/check-css-vars.ts
|
||||
/// <reference path="./modules.d.ts" />
|
||||
import { readdirSync, readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { compile as compileSass } from 'sass'
|
||||
import postcss from 'postcss'
|
||||
import prefixCustomProperties from 'postcss-prefix-custom-properties'
|
||||
import { cssVarIgnore, cssVarPrefix, inlineValueComments } from './css-var-prefix'
|
||||
|
||||
const scssDir = 'core/scss'
|
||||
const baselineFile = '.build/css-vars-baseline.txt'
|
||||
|
||||
const definition = /(--[\w-]+)\s*:/g
|
||||
const reference = /var\(\s*(--[\w-]+)\s*([,)])/g
|
||||
const registration = /@property\s+(--[\w-]+)/g
|
||||
|
||||
const defined = new Set<string>()
|
||||
// name → stylesheets referencing it without a fallback
|
||||
const referenced = new Map<string, Set<string>>()
|
||||
|
||||
async function scan(entry: string) {
|
||||
const compiled = compileSass(join(scssDir, entry), { loadPaths: ['node_modules'], style: 'expanded' })
|
||||
const { css } = await postcss([inlineValueComments, prefixCustomProperties({ prefix: cssVarPrefix, ignore: cssVarIgnore })]).process(compiled.css, { from: undefined, map: false })
|
||||
const file = entry.replace('.scss', '.css')
|
||||
|
||||
for (const [, name] of css.matchAll(definition)) defined.add(name)
|
||||
for (const [, name] of css.matchAll(registration)) defined.add(name)
|
||||
for (const [, name, next] of css.matchAll(reference)) {
|
||||
if (next !== ')') continue
|
||||
if (!name.startsWith(`--${cssVarPrefix}`)) continue
|
||||
const files = referenced.get(name) ?? new Set<string>()
|
||||
files.add(file)
|
||||
referenced.set(name, files)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const entries = readdirSync(scssDir).filter((file) => file.endsWith('.scss') && !file.startsWith('_'))
|
||||
for (const entry of entries) await scan(entry)
|
||||
|
||||
const dangling = new Map([...referenced].filter(([name]) => !defined.has(name)).sort())
|
||||
const baseline = readFileSync(baselineFile, 'utf8')
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.startsWith('--'))
|
||||
|
||||
const added = [...dangling.keys()].filter((name) => !baseline.includes(name))
|
||||
const fixed = baseline.filter((name) => !dangling.has(name))
|
||||
|
||||
for (const name of added) {
|
||||
console.error(`✗ ${name} — referenced by ${[...dangling.get(name)!].join(', ')}, defined nowhere`)
|
||||
}
|
||||
if (added.length > 0) {
|
||||
console.error(`\n${added.length} new dangling custom ${added.length === 1 ? 'property' : 'properties'}. Define the property, give the var() a fallback, or drop the declaration.`)
|
||||
}
|
||||
if (fixed.length > 0) {
|
||||
console.error(`\n${fixed.join(', ')} no longer dangling — delete ${fixed.length === 1 ? 'that line' : 'those lines'} from ${baselineFile}.`)
|
||||
}
|
||||
if (added.length > 0 || fixed.length > 0) process.exit(1)
|
||||
|
||||
console.log(`OK — ${referenced.size} custom properties referenced without a fallback, all defined (${baseline.length} known exceptions in ${baselineFile}).`)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
# Custom properties the css references but never defines. Each one makes the
|
||||
# declaration that reads it invalid, so the browser drops it - see
|
||||
# check-css-vars.ts. The check fails on any name that is not listed here, and
|
||||
# also on a listed name that no longer dangles, so fixing one means deleting
|
||||
# its line.
|
||||
#
|
||||
# Everything below is a real bug waiting to be fixed, not an accepted exception.
|
||||
|
||||
# `.list-group-item-{color}` reads subtle/emphasis tokens that bootstrap only
|
||||
# builds for $theme-colors, so these variants render with no background and no
|
||||
# text color at all.
|
||||
--tblr-azure-bg-subtle
|
||||
--tblr-azure-border-subtle
|
||||
--tblr-azure-text-emphasis
|
||||
--tblr-blue-bg-subtle
|
||||
--tblr-blue-border-subtle
|
||||
--tblr-blue-text-emphasis
|
||||
--tblr-cyan-bg-subtle
|
||||
--tblr-cyan-border-subtle
|
||||
--tblr-cyan-text-emphasis
|
||||
--tblr-green-bg-subtle
|
||||
--tblr-green-border-subtle
|
||||
--tblr-green-text-emphasis
|
||||
--tblr-indigo-bg-subtle
|
||||
--tblr-indigo-border-subtle
|
||||
--tblr-indigo-text-emphasis
|
||||
--tblr-lime-bg-subtle
|
||||
--tblr-lime-border-subtle
|
||||
--tblr-lime-text-emphasis
|
||||
--tblr-muted-bg-subtle
|
||||
--tblr-muted-border-subtle
|
||||
--tblr-muted-text-emphasis
|
||||
--tblr-orange-bg-subtle
|
||||
--tblr-orange-border-subtle
|
||||
--tblr-orange-text-emphasis
|
||||
--tblr-pink-bg-subtle
|
||||
--tblr-pink-border-subtle
|
||||
--tblr-pink-text-emphasis
|
||||
--tblr-purple-bg-subtle
|
||||
--tblr-purple-border-subtle
|
||||
--tblr-purple-text-emphasis
|
||||
--tblr-red-bg-subtle
|
||||
--tblr-red-border-subtle
|
||||
--tblr-red-text-emphasis
|
||||
--tblr-teal-bg-subtle
|
||||
--tblr-teal-border-subtle
|
||||
--tblr-teal-text-emphasis
|
||||
--tblr-yellow-bg-subtle
|
||||
--tblr-yellow-border-subtle
|
||||
--tblr-yellow-text-emphasis
|
||||
|
||||
# tabler-marketing.css: `.shape-*` backgrounds, borders and headings reach for
|
||||
# tokens that only ever existed in the marketing prototype.
|
||||
--tblr-black-lt
|
||||
--tblr-border-color-dark
|
||||
--tblr-border-color-light
|
||||
--tblr-border-radius-circle
|
||||
--tblr-font-weight-black
|
||||
--tblr-gray-dark-lt
|
||||
--tblr-gray-lt
|
||||
|
||||
# `body { text-align }` - bootstrap leaves the token unset by default, so the
|
||||
# declaration never applies.
|
||||
--tblr-body-text-align
|
||||
|
||||
# `.nav-link` font size and the active nav link color (#2287): both drop, so a
|
||||
# nav link takes the body font size and keeps the inherited color when active.
|
||||
--tblr-nav-link-active-color
|
||||
--tblr-nav-link-font-size
|
||||
+2
-1
@@ -20,7 +20,8 @@
|
||||
"import-payments": "tsx .build/import-payments.ts",
|
||||
"generate-tokens:check": "tsx .build/generate-tokens.ts check",
|
||||
"check-docs-links": "tsx .build/check-docs-links.ts",
|
||||
"lint": "pnpm run lint-md && pnpm run check-docs-links && pnpm run lint-prettier && pnpm run lint-scss-vars && pnpm run generate-tokens:check",
|
||||
"check-css-vars": "tsx .build/check-css-vars.ts",
|
||||
"lint": "pnpm run lint-md && pnpm run check-docs-links && pnpm run lint-prettier && pnpm run lint-scss-vars && pnpm run check-css-vars && pnpm run generate-tokens:check",
|
||||
"lint:fix": "(pnpm run lint-md-fix || true) && (pnpm run lint:scss:fix || true) && (pnpm run format-prettier || true)",
|
||||
"lint:scss": "pnpm --filter @tabler/core lint:scss",
|
||||
"lint:scss:fix": "pnpm --filter @tabler/core lint:scss:fix",
|
||||
|
||||
Reference in new issue
Block a user