mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 22:45:02 +01:00
design-lint: name the tracked-caps thresholds once, string-aware brace scan, drop an unused parameter, clean up test tmpdirs
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
31682440fe
commit
2bf81ad1f1
13 files changed
+68
-25
No files matched your search
@@ -90,6 +90,10 @@ function nestSelector(sel: string, parent: string): string {
|
||||
/** Faces that read as monospace, whether they arrive as a token, a utility or a declaration; one list so every check classifies a face the same way. */
|
||||
export const MONO_FACE = /mono|code|courier|silkscreen|jetbrains|plex mono|geist mono|commit|fira code|space mono|dm mono|roboto mono|source code|ibm plex mono|press start|vt323|share tech/i;
|
||||
|
||||
/** The tracked-caps label: tracking past this (em) on text at or under LABEL_PX reads as the reflex, in CSS and in Tailwind alike. */
|
||||
export const TRACKED_EM = 0.03;
|
||||
export const LABEL_PX = 15;
|
||||
|
||||
/** Real stylesheets nest a handful of levels; past this the rest reads as the parent's body instead of recursing. */
|
||||
const MAX_NESTING = 64;
|
||||
|
||||
@@ -125,8 +129,10 @@ export function parseCss(source: string, file: string, lineOffset = 0): CssRule[
|
||||
const blockStart = j + 1;
|
||||
let depth = 1, k = blockStart;
|
||||
while (k < end && depth > 0) {
|
||||
if (src[k] === '{') depth++;
|
||||
else if (src[k] === '}') depth--;
|
||||
const ch = src[k];
|
||||
if (ch === '"' || ch === "'") { const q = src.indexOf(ch, k + 1); k = q < 0 || q >= end ? end : q + 1; continue; }
|
||||
if (ch === '{') depth++;
|
||||
else if (ch === '}') depth--;
|
||||
k++;
|
||||
}
|
||||
const blockEnd = Math.max(blockStart, k - 1);
|
||||
|
||||
@@ -47,7 +47,7 @@ function reportItem(ctx: LintContext, r: Reporter, it: TwItem, monoOk: boolean):
|
||||
}
|
||||
|
||||
/** The tells a file carries as a whole: a default palette, hover scale everywhere, a vh hero, three icon tiles. */
|
||||
function reportFile(ctx: LintContext, r: Reporter, list: TwItem[]): void {
|
||||
function reportFile(r: Reporter, list: TwItem[]): void {
|
||||
const doc = list[0].doc;
|
||||
const pal = new Map<string, TwItem>();
|
||||
let palAt: TwItem | null = null;
|
||||
@@ -78,7 +78,7 @@ function checkTailwind(ctx: LintContext, r: Reporter): void {
|
||||
byDoc.get(it.file)!.push(it);
|
||||
reportItem(ctx, r, it, monoOk);
|
||||
}
|
||||
for (const list of byDoc.values()) reportFile(ctx, r, list);
|
||||
for (const list of byDoc.values()) reportFile(r, list);
|
||||
}
|
||||
|
||||
// sloplint checkComponentText (gates 63 30a)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { OVERUSED_FONTS_DISPLAY } from '../../design-catalog';
|
||||
import type { LintContext } from '../context';
|
||||
import { fontPx, mergedRules, styleFor, trackingEm, valueOf } from '../context';
|
||||
import { MONO_FACE, famKey, firstFamily, GENERIC_FAMILIES, HEADING_SEL, isTokenRule, pxOf, resolveVars, type CssDecl, type CssRule, type MergedRule } from '../css';
|
||||
import { GENERIC_FAMILIES, HEADING_SEL, LABEL_PX, MONO_FACE, TRACKED_EM, famKey, firstFamily, isTokenRule, pxOf, resolveVars, type CssDecl, type CssRule, type MergedRule } from '../css';
|
||||
import { ancestorMap, getAttr, innerOf, plainText } from '../html';
|
||||
import type { Reporter } from '../report';
|
||||
import { trunc } from '../report';
|
||||
@@ -272,7 +272,7 @@ function caps67(ctx: LintContext, bySel: MergedRule[], rule: MergedRule): Caps67
|
||||
const ownSmallCaps = rule.decls.some((d) => isSmallCaps67(d.prop, resolveVars(d.value, ctx.tokens)));
|
||||
const px = fontSizePxOf(rule, ctx);
|
||||
const ownEm = trackingEm(ctx, rule.decls, px || 16);
|
||||
if (!(ownUpper === true || ownSmallCaps || (ownEm != null && ownEm > 0.03))) return null;
|
||||
if (!(ownUpper === true || ownSmallCaps || (ownEm != null && ownEm > TRACKED_EM))) return null;
|
||||
const upper = ownUpper != null ? ownUpper : isUpper(inherited67(bySel, rule.selector, 'text-transform'));
|
||||
const smallCaps = ownSmallCaps || ['font-variant', 'font-variant-caps', 'font-feature-settings'].some((prop) => {
|
||||
const v = inherited67(bySel, rule.selector, prop);
|
||||
@@ -298,7 +298,7 @@ function check67(ctx: LintContext, r: Reporter): void {
|
||||
const caps = caps67(ctx, bySel, rule);
|
||||
if (!caps) continue;
|
||||
const { upper, smallCaps, em, px } = caps;
|
||||
const tracked = em != null && em > 0.03;
|
||||
const tracked = em != null && em > TRACKED_EM;
|
||||
const subject = rule.selector.split(',').map((p) => baseSelector(p).split(/[\s>+~]+/).filter(Boolean).pop() || '');
|
||||
const bigToken = rule.decls.some((d) => d.prop === 'font-size' && /--text-(display|section|title)|clamp\(\s*(?:[2-9]|\d{2,})(?:\.\d+)?rem/.test(d.value));
|
||||
const labelish = subject.some((s) => LABEL67.test(s)) ? !((px != null && px >= 28) || bigToken)
|
||||
@@ -307,7 +307,7 @@ function check67(ctx: LintContext, r: Reporter): void {
|
||||
if (HEADING_SEL.test(rule.selector) && !labelish && !control) continue;
|
||||
const key = baseSelector(rule.selector);
|
||||
if (seen.has(key)) continue;
|
||||
const small = px != null && px <= 15;
|
||||
const small = px != null && px <= LABEL_PX;
|
||||
const at = px != null ? ` at ${Math.round(px)}px` : '';
|
||||
const tracking = tracked ? ` tracked ${em3(em)}em` : '';
|
||||
if (control && !labelish) {
|
||||
@@ -352,8 +352,8 @@ function check67Html(ctx: LintContext, r: Reporter): void {
|
||||
const decls = styleFor(ctx, cls, t.name);
|
||||
const px = fontPx(ctx, decls);
|
||||
const em = trackingEm(ctx, decls, px || 16);
|
||||
const tracked = em != null && em > 0.03;
|
||||
if (!((px != null && px <= 15) || tracked)) continue;
|
||||
const tracked = em != null && em > TRACKED_EM;
|
||||
if (!((px != null && px <= LABEL_PX) || tracked)) continue;
|
||||
const key = cls || t.name;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import type { LintContext } from './context';
|
||||
import { memo } from './context';
|
||||
import { COLOR_LITERAL, hueIn, oklchOf, parseColor, type Color } from './color';
|
||||
import { MONO_FACE, GENERIC_FAMILIES, firstFamily, pxOf, resolveVars, splitTop, type Tokens } from './css';
|
||||
import { GENERIC_FAMILIES, LABEL_PX, MONO_FACE, TRACKED_EM, firstFamily, pxOf, resolveVars, splitTop, type Tokens } from './css';
|
||||
import { braceEnd, closeIndex, clsLits, directChildren, getAttr, innerOf, plainText, type Doc, type Tag } from './html';
|
||||
import { trunc } from './report';
|
||||
|
||||
@@ -619,13 +619,13 @@ export interface TwTrackedCaps { what: string; size: number | null; em: number |
|
||||
/** The tracked-caps label: small text set uppercase, small caps or letter-spaced past 0.03em, outside logos, code and display heads. */
|
||||
export function twTrackedCaps(it: TwItem, tw: TwGroup): TwTrackedCaps | null {
|
||||
const f = it.info, fr = it.frame;
|
||||
if (!(f.upper || f.smallCaps || (f.trackingEm != null && f.trackingEm > 0.03))) return null;
|
||||
if (!(f.upper || f.smallCaps || (f.trackingEm != null && f.trackingEm > TRACKED_EM))) return null;
|
||||
if (fr.logo || fr.logoSelf || fr.noLabel) return null;
|
||||
const size = fr.size, em = fr.em;
|
||||
if ((size != null && size >= 24) || (size == null && /^h[1-6]$/.test(it.tag))) return null;
|
||||
const caps = fr.upper || fr.caps;
|
||||
const small = size != null && size <= 15;
|
||||
const tracked = em != null && em > 0.03;
|
||||
const small = size != null && size <= LABEL_PX;
|
||||
const tracked = em != null && em > TRACKED_EM;
|
||||
const control = /^(?:button|input)$/.test(it.tag) || /(?:button|btn|cta)$/i.test(it.tag) || (it.tag === 'a' && f.fill && f.padded) || /role\s*=\s*["']?button/i.test(it.attrs);
|
||||
if (tw.upperDisplay && control && (em == null || em <= 0.04)) return null;
|
||||
if (!((caps && (small || tracked)) || (tracked && small))) return null;
|
||||
@@ -638,7 +638,7 @@ export interface TwEyebrow { text: string; next: string; signal: string }
|
||||
export function twEyebrow(it: TwItem): TwEyebrow | null {
|
||||
const f = it.info, fr = it.frame, doc = it.doc;
|
||||
if (it.idx < 0 || !/^(?:p|span|div|small)$/.test(it.tag) || fr.eyeSkip) return null;
|
||||
if (!(f.upper || (f.trackingEm != null && f.trackingEm > 0.03) || f.accent)) return null;
|
||||
if (!(f.upper || (f.trackingEm != null && f.trackingEm > TRACKED_EM) || f.accent)) return null;
|
||||
if (fr.size == null || fr.size > 14) return null;
|
||||
const t = doc.tags[it.idx];
|
||||
if (/role\s*=\s*["']?status|aria-live|aria-current|aria-hidden\s*=\s*["']?true/i.test(t.attrs)) return null;
|
||||
@@ -650,7 +650,7 @@ export function twEyebrow(it: TwItem): TwEyebrow | null {
|
||||
const href = /<a\b[^>]*\shref\s*=\s*["']([^"']*)["']/i.exec(inner);
|
||||
if (href && !/^#?$/.test(href[1].trim())) return null;
|
||||
const text = twPlain(doc, inner);
|
||||
const signal = [f.upper ? 'uppercase' : null, f.trackingEm != null && f.trackingEm > 0.03 ? `tracked ${Math.round(f.trackingEm * 1000) / 1000}em` : null, f.accent].filter(Boolean).join(', ');
|
||||
const signal = [f.upper ? 'uppercase' : null, f.trackingEm != null && f.trackingEm > TRACKED_EM ? `tracked ${Math.round(f.trackingEm * 1000) / 1000}em` : null, f.accent].filter(Boolean).join(', ');
|
||||
if (!text) return doc.kind !== 'html' && c > it.idx && doc.raw.slice(t.end, doc.tags[c].pos).includes('{') ? { text: '', next: nx.name, signal } : null;
|
||||
const words = text.split(/[\s·/|\u2014\u2013]+/).filter(Boolean);
|
||||
if (words.length > 7 || text.length > 56 || /[\w/-]+\.[a-z]{1,4}$/i.test(text)) return null;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* for every rule id, one page that is clean for it and one with the tell
|
||||
* planted, checked by id, grade and line.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
@@ -11,6 +11,9 @@ import { lint } from '../lib/design-lint/index';
|
||||
import { formatFinding, type Finding } from '../lib/design-lint/report';
|
||||
import { lintStatic } from '../lib/design-lint/static';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
|
||||
@@ -28,6 +31,7 @@ const lineOf = (html: string, needle: string): number => {
|
||||
|
||||
function run(html: string, files: Record<string, string> = {}): Finding[] {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-assets-'));
|
||||
tmpDirs.push(dir);
|
||||
fs.writeFileSync(path.join(dir, 'index.html'), html);
|
||||
for (const [name, text] of Object.entries(files)) fs.writeFileSync(path.join(dir, name), text);
|
||||
return lintStatic([dir], { cwd: dir, warn: (l) => { throw new Error(l); } }).findings;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* chrome-copy.ts, chrome-eyebrow.ts): for every rule id, one page that is
|
||||
* clean for it and one with the tell planted, checked by id, grade and line.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
@@ -11,6 +11,9 @@ import { lint } from '../lib/design-lint/index';
|
||||
import { formatFinding, type Finding } from '../lib/design-lint/report';
|
||||
import { lintStatic } from '../lib/design-lint/static';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
|
||||
@@ -29,6 +32,7 @@ const lineOf = (html: string, needle: string): number => {
|
||||
|
||||
function run(html: string, opts: { job?: string | null; scope?: 'page' | 'component'; files?: Record<string, string> } = {}): Finding[] {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-chrome-'));
|
||||
tmpDirs.push(dir);
|
||||
const { files = {}, ...rest } = opts;
|
||||
fs.writeFileSync(path.join(dir, 'index.html'), html);
|
||||
for (const [name, text] of Object.entries(files)) fs.writeFileSync(path.join(dir, name), text);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { formatFinding, type Finding } from '../lib/design-lint/report';
|
||||
import { lintStatic } from '../lib/design-lint/static';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
|
||||
@@ -31,6 +34,7 @@ const page = (css: string, body = '<h1>Ledger</h1><p>The books close on the firs
|
||||
|
||||
function lintPage(html: string, name = 'index.html'): { html: string; findings: Finding[]; dir: string } {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-color-'));
|
||||
tmpDirs.push(dir);
|
||||
const file = path.join(dir, name);
|
||||
fs.writeFileSync(file, html);
|
||||
return { html, dir, findings: lintStatic([file], { cwd: dir, warn: (l) => { throw new Error(l); } }).findings };
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* planted, checked by id, grade and line. Everything here prints at polish
|
||||
* except fake-avatar, which blocks.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
@@ -12,6 +12,9 @@ import { lint } from '../lib/design-lint/index';
|
||||
import { formatFinding, type Finding } from '../lib/design-lint/report';
|
||||
import { lintStatic } from '../lib/design-lint/static';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
|
||||
@@ -29,6 +32,7 @@ const lineOf = (html: string, needle: string): number => {
|
||||
|
||||
function run(html: string, files: Record<string, string> = {}): Finding[] {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-finish-'));
|
||||
tmpDirs.push(dir);
|
||||
fs.writeFileSync(path.join(dir, 'index.html'), html);
|
||||
for (const [name, text] of Object.entries(files)) fs.writeFileSync(path.join(dir, name), text);
|
||||
return lintStatic([dir], { cwd: dir, warn: (l) => { throw new Error(l); } }).findings;
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
* planted positive with its line and grade, and a negative control at the edge of the threshold. The shared fixtures
|
||||
* (slopped.html, clean/) are checked for this family's rules too.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { formatFinding, lint, type Finding } from '../lib/design-lint/index';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
const FAMILY = ['layout-transition', 'spacing-transition', 'hover-tells', 'bounce-easing', 'focus-ring-fade', 'tooltip-focus-delay', 'autorotate-no-pause', 'missing-focus-visible', 'missing-states', 'missing-reduced-motion', 'autoplay-unmuted', 'lazy-lcp', 'video-without-poster', 'input-state-shift', 'control-height', 'content-hidden-at-rest'];
|
||||
@@ -24,6 +27,7 @@ function page(css: string, body: string, opts: { states?: boolean; reduce?: bool
|
||||
|
||||
async function lintPage(html: string, opts: { scope?: 'page' | 'component' } = {}): Promise<Finding[]> {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-motion-'));
|
||||
tmpDirs.push(dir);
|
||||
const file = path.join(dir, 'index.html');
|
||||
fs.writeFileSync(file, html);
|
||||
return (await lint([file], { cwd: dir, scope: opts.scope, warn: () => {} })).findings;
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
* positive with its line and grade, and a negative control at the edge of the threshold. The shared fixtures
|
||||
* (slopped.html, clean/) are checked for this family's rules too.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { formatFinding, lint, type Finding } from '../lib/design-lint/index';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
const FAMILY = ['template-cards', 'nested-cards', 'vh-height', 'raw-spacing', 'off-scale-spacing', 'overflow-x-unclipped', 'wide-container', 'bare-fr-track', 'display-head-unwrapped', 'radio-tab-jump', 'double-sticky', 'screen-word-budget'];
|
||||
@@ -24,6 +27,7 @@ function page(css: string, body: string, opts: { clip?: boolean } = {}): string
|
||||
|
||||
async function lintPage(html: string, opts: { scope?: 'page' | 'component'; name?: string } = {}): Promise<Finding[]> {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-structure-'));
|
||||
tmpDirs.push(dir);
|
||||
const file = path.join(dir, opts.name ?? 'index.html');
|
||||
fs.writeFileSync(file, html);
|
||||
return (await lint([file], { cwd: dir, scope: opts.scope, warn: () => {} })).findings;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* strings in HTML, JSX, Vue, Svelte and Astro, helper calls, shared tokens)
|
||||
* and the rules it feeds, each with a negative control and a planted tell.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
@@ -11,6 +11,9 @@ import { lint, formatFinding, type Finding } from '../lib/design-lint/index';
|
||||
import { lintStatic } from '../lib/design-lint/static';
|
||||
import { twGroup, twParse, twVariants } from '../lib/design-lint/tailwind';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
|
||||
@@ -18,6 +21,7 @@ const MY_RULES = ['layout-transition', 'bounce-easing', 'purple-blue-gradient',
|
||||
|
||||
function tmpDir(files: Record<string, string>): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-tw-'));
|
||||
tmpDirs.push(dir);
|
||||
for (const [name, text] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(path.join(dir, name)), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, name), text);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { formatFinding, type Finding } from '../lib/design-lint/report';
|
||||
import { lintStatic } from '../lib/design-lint/static';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
|
||||
@@ -34,6 +37,7 @@ const page = (css: string, body = '<h1>Ledger</h1><p>The books close on the firs
|
||||
|
||||
function lintPage(html: string, name = 'index.html'): { html: string; findings: Finding[]; dir: string } {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-type-'));
|
||||
tmpDirs.push(dir);
|
||||
const file = path.join(dir, name);
|
||||
fs.writeFileSync(file, html);
|
||||
return { html, dir, findings: lintStatic([file], { cwd: dir, warn: (l) => { throw new Error(l); } }).findings };
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* (test/design-lint-<family>.test.ts); the render tier is
|
||||
* test/design-lint-render.test.ts.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
@@ -19,6 +19,9 @@ import { RULES, RULE_IDS } from '../lib/design-lint/rules/ids';
|
||||
import { FAMILIES } from '../lib/design-lint/rules';
|
||||
import { findWaivers, reasonIsBoilerplate, resolveWaivers, applyWaivers, suspectedRules } from '../lib/design-lint/waivers';
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
afterAll(() => { for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true }); });
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const BIN = path.join(ROOT, 'bin', 'gstack-design-lint');
|
||||
const FIXTURES = path.join(ROOT, 'test', 'fixtures', 'design-lint');
|
||||
@@ -30,6 +33,7 @@ function runBin(args: string[], cwd = ROOT) {
|
||||
|
||||
function tmpPage(html: string, name = 'index.html'): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-'));
|
||||
tmpDirs.push(dir);
|
||||
const file = path.join(dir, name);
|
||||
fs.writeFileSync(file, html);
|
||||
return file;
|
||||
@@ -38,6 +42,7 @@ function tmpPage(html: string, name = 'index.html'): string {
|
||||
/** A temporary tree: each key is a relative path (parent directories are created), each value the file's text. */
|
||||
function tmpTree(files: Record<string, string>): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-lint-tree-'));
|
||||
tmpDirs.push(dir);
|
||||
for (const [rel, text] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, rel), text);
|
||||
|
||||
Reference in new issue
Block a user