mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-09 19:11:28 -05:00
Add font-weight token validation to stylelint for design-system area
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -95,7 +95,32 @@ Agents window ramp (`src/vs/sessions/**`) — pair size with a weight token,
|
||||
|
||||
Weights: `--vscode-agents-fontWeight-regular` (400),
|
||||
`--vscode-agents-fontWeight-semiBold` (600). The ramp is **400/600 only** — there
|
||||
is no medium (500). "Strong" = same size token + `semiBold`.
|
||||
is no medium (500). "Strong" = same size token + `semiBold`. See
|
||||
[Font weight](#font-weight--font-weight) below.
|
||||
|
||||
## Font weight — `font-weight`
|
||||
|
||||
The agents window uses a **two-weight ramp** — there are no other weights.
|
||||
Pair every text style with one of these:
|
||||
|
||||
| weight | Variable | Use |
|
||||
|--------|----------|-----|
|
||||
| 400 | `--vscode-agents-fontWeight-regular` | body, labels, metadata |
|
||||
| 600 | `--vscode-agents-fontWeight-semiBold` | headings, "strong" emphasis |
|
||||
|
||||
- **No medium (500).** `font-weight: 500` is **off the ramp** — snap it to
|
||||
`semiBold` (600). The same goes for `700`/`bold` and any other numeric weight:
|
||||
round to the nearer of 400/600.
|
||||
- **"Strong" is not a separate size.** A "Body 1 Strong" / "Label 2 Strong"
|
||||
style reuses the matching `--vscode-agents-fontSize-*` token paired with
|
||||
`semiBold`. Never introduce a separate strong *size* token.
|
||||
- `normal` ≡ 400 → `regular`. **Leave untouched:** `inherit`, `lighter`,
|
||||
`bolder`, and any `var()`/`calc()` expression. Preserve `!important`.
|
||||
|
||||
```css
|
||||
/* avoid */ font-weight: 500; /* not on the 400/600 ramp */
|
||||
/* prefer */ font-weight: var(--vscode-agents-fontWeight-semiBold);
|
||||
```
|
||||
|
||||
## Codicon size — icon `font-size`
|
||||
|
||||
|
||||
@@ -264,3 +264,94 @@ export function validateCornerRadiusTokens(text: string): IDesignTokenViolation[
|
||||
|
||||
return violations;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Font-weight token suggestions (sessions design-system area only)
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// The agents font ramp defines exactly two weights: regular (400) and
|
||||
// semiBold (600). Any other numeric weight (e.g. 500, 700) is off the ramp and
|
||||
// snaps to the nearer of the two. The CSS keywords `normal` (400) and `bold`
|
||||
// (700) are normalised before snapping. `inherit`, `lighter`, `bolder` and
|
||||
// var()/calc() expressions are left untouched.
|
||||
|
||||
const RE_FONT_WEIGHT = /font-weight\s*:\s*([^;{}]+)/i;
|
||||
|
||||
interface IFontWeightToken {
|
||||
readonly weight: number;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
/** The only two weights in the agents ramp. */
|
||||
const FONT_WEIGHT_TOKENS: readonly IFontWeightToken[] = [
|
||||
{ weight: 400, name: 'regular' },
|
||||
{ weight: 600, name: 'semiBold' },
|
||||
];
|
||||
|
||||
/** Resolves a font-weight value to a number, or undefined if not numeric. */
|
||||
function parseFontWeight(value: string): number | undefined {
|
||||
const trimmed = value.trim().toLowerCase();
|
||||
if (trimmed === 'normal') {
|
||||
return 400;
|
||||
}
|
||||
if (trimmed === 'bold') {
|
||||
return 700;
|
||||
}
|
||||
const numeric = /^(\d{3})$/.exec(trimmed);
|
||||
return numeric ? parseInt(numeric[1], 10) : undefined;
|
||||
}
|
||||
|
||||
/** Maps a numeric weight to its token, snapping off-ramp values to the nearer. */
|
||||
function snapFontWeight(weight: number): IFontWeightToken {
|
||||
let best = FONT_WEIGHT_TOKENS[0];
|
||||
let bestDistance = Math.abs(best.weight - weight);
|
||||
for (const token of FONT_WEIGHT_TOKENS) {
|
||||
const distance = Math.abs(token.weight - weight);
|
||||
// `<=` makes the 500 tie prefer the heavier (600) token.
|
||||
if (distance <= bestDistance) {
|
||||
best = token;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds hardcoded `font-weight` values and suggests the agents weight-ramp var.
|
||||
* Exact ramp values (400 / 600, plus `normal` = 400) are flagged as drop-in
|
||||
* replacements; off-ramp values (e.g. 500, 700, `bold`) snap to the nearer
|
||||
* token and call out that the value is off the two-weight ramp. `inherit`,
|
||||
* `lighter`, `bolder` and var()/calc() expressions are ignored. Returns one
|
||||
* finding per occurrence so the terminal can linkify each `file(line,col)`.
|
||||
*/
|
||||
export function validateFontWeightTokens(text: string): IDesignTokenViolation[] {
|
||||
const violations: IDesignTokenViolation[] = [];
|
||||
|
||||
forEachDeclaration(text, (line, _selector, declaration) => {
|
||||
const decl = RE_FONT_WEIGHT.exec(declaration);
|
||||
if (!decl) {
|
||||
return;
|
||||
}
|
||||
const value = decl[1];
|
||||
if (/var\(|calc\(/i.test(value)) {
|
||||
return;
|
||||
}
|
||||
const weight = parseFontWeight(value);
|
||||
if (weight === undefined) {
|
||||
return;
|
||||
}
|
||||
const token = snapFontWeight(weight);
|
||||
const exact = token.weight === weight;
|
||||
const shown = value.trim();
|
||||
const headline = exact
|
||||
? `font-weight ${shown} matches a design ramp token - prefer the var:`
|
||||
: `font-weight ${shown} is off the agents weight ramp (400/600) - nearest token (${token.weight}):`;
|
||||
violations.push({
|
||||
line,
|
||||
isNearMiss: !exact,
|
||||
message: `${headline}\n -> use var(--vscode-agents-fontWeight-${token.name})`
|
||||
});
|
||||
});
|
||||
|
||||
return violations;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import es from 'event-stream';
|
||||
import vfs from 'vinyl-fs';
|
||||
import { stylelintFilter } from './filters.ts';
|
||||
import { getVariableNameValidator } from './lib/stylelint/validateVariableNames.ts';
|
||||
import { validateCodiconFontSizes, validateFontSizeTokens, validateCornerRadiusTokens } from './lib/stylelint/validateDesignTokens.ts';
|
||||
import { validateCodiconFontSizes, validateFontSizeTokens, validateFontWeightTokens, validateCornerRadiusTokens } from './lib/stylelint/validateDesignTokens.ts';
|
||||
|
||||
interface FileWithLines {
|
||||
__lines?: string[];
|
||||
@@ -61,6 +61,9 @@ export default function gulpstylelint(reporter: Reporter): NodeJS.ReadWriteStrea
|
||||
for (const violation of validateFontSizeTokens(contents)) {
|
||||
reporter(file.relative + '(' + violation.line + ',1): ' + violation.message, false);
|
||||
}
|
||||
for (const violation of validateFontWeightTokens(contents)) {
|
||||
reporter(file.relative + '(' + violation.line + ',1): ' + violation.message, false);
|
||||
}
|
||||
for (const violation of validateCornerRadiusTokens(contents)) {
|
||||
reporter(file.relative + '(' + violation.line + ',1): ' + violation.message, false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user