mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Fix: inherit DESIGN.md only from a monorepo root that owns the path (#570)
findDesignRoot continued past every workspace package.json to any workspace-declaring ancestor. It now matches the boundary against that ancestor's globs (including negations and globstars), so excluded and stray packages do not inherit, while included workspaces still do. Written with AI assistance (Cursor); reviewed by maintainer. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -586,10 +586,13 @@ function designSystemStartDir(targetPath, cwd = process.cwd()) {
|
||||
// - A directory carrying a DESIGN.md (directly or in a fallback dir) IS the
|
||||
// design root — that's where the rules live.
|
||||
// - A directory carrying a project marker (.git / package.json / .impeccable)
|
||||
// but no DESIGN.md is a project BOUNDARY. When that boundary is a monorepo
|
||||
// workspace (owned by a workspace-declaring root above it, recognized the
|
||||
// same way context.mjs does), the workspace inherits the monorepo root's
|
||||
// DESIGN.md; a nested separate repository (.git with no workspace
|
||||
// but no DESIGN.md is a project BOUNDARY. A nested package.json inherits
|
||||
// the ancestor DESIGN.md only when that ancestor's workspace declarations
|
||||
// include the path (negations win). Marker-only roots (turbo/nx/lerna/pnpm
|
||||
// with no globs) still own apps/<name> and packages/<name>. A stray nested
|
||||
// package that matches no glob does not inherit. This is detect's
|
||||
// contamination contract, not skill-context's repoRoot fallback for
|
||||
// excluded paths. A nested separate repository (.git with no workspace
|
||||
// declaration) still inherits nothing (issue #570).
|
||||
// - Reaching the home directory / filesystem root with neither means no
|
||||
// design system at all — never process.cwd()'s.
|
||||
@@ -643,6 +646,76 @@ function isMonorepoRoot(dir) {
|
||||
});
|
||||
}
|
||||
|
||||
function monorepoOwnsPath(root, boundaryDir) {
|
||||
const rel = path.relative(root, boundaryDir);
|
||||
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return false;
|
||||
const relSegments = rel.split(path.sep).filter(Boolean);
|
||||
|
||||
function normalizeWorkspacePattern(pattern) {
|
||||
return String(pattern || '')
|
||||
.trim()
|
||||
.replace(/^['"]|['"]$/g, '')
|
||||
.replace(/^\.\//, '')
|
||||
.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function escapeRegExp(s) {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
function segmentMatches(patternSegment, relSegment) {
|
||||
if (patternSegment === '*') return true;
|
||||
if (!patternSegment.includes('*')) return patternSegment === relSegment;
|
||||
const re = new RegExp(`^${escapeRegExp(patternSegment).replace(/\\\*/g, '[^/]*')}$`);
|
||||
return re.test(relSegment);
|
||||
}
|
||||
|
||||
function matchGlobSegments(patternSegments, relSegments) {
|
||||
function rec(pi, ri) {
|
||||
if (pi === patternSegments.length) return ri === relSegments.length;
|
||||
if (patternSegments[pi] === '**') {
|
||||
if (pi === patternSegments.length - 1) return true;
|
||||
for (let k = ri; k <= relSegments.length; k++) {
|
||||
if (rec(pi + 1, k)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (ri >= relSegments.length) return false;
|
||||
if (!segmentMatches(patternSegments[pi], relSegments[ri])) return false;
|
||||
return rec(pi + 1, ri + 1);
|
||||
}
|
||||
return rec(0, 0);
|
||||
}
|
||||
|
||||
// allowPrefix: negations like !packages/excluded must also cover nested dirs
|
||||
// under that path. Positive globs are exact (npm `*` is direct children only).
|
||||
function matches(pattern, { allowPrefix = false } = {}) {
|
||||
const patternSegments = normalizeWorkspacePattern(pattern).split('/').filter(Boolean);
|
||||
if (!patternSegments.length) return false;
|
||||
if (patternSegments.includes('**')) return matchGlobSegments(patternSegments, relSegments);
|
||||
if (allowPrefix) {
|
||||
if (relSegments.length < patternSegments.length) return false;
|
||||
} else if (relSegments.length !== patternSegments.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < patternSegments.length; i++) {
|
||||
if (!segmentMatches(patternSegments[i], relSegments[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const patterns = readWorkspacePatterns(root)
|
||||
.map(normalizeWorkspacePattern)
|
||||
.filter(Boolean);
|
||||
if (patterns.some((pattern) => pattern.startsWith('!') && matches(pattern.slice(1), { allowPrefix: true }))) {
|
||||
return false;
|
||||
}
|
||||
const positive = patterns.filter((pattern) => !pattern.startsWith('!'));
|
||||
if (positive.some((pattern) => matches(pattern))) return true;
|
||||
if (positive.length) return false;
|
||||
return relSegments.length >= 2 && MONOREPO_FALLBACK_PROJECT_DIRS.includes(relSegments[0]);
|
||||
}
|
||||
|
||||
// Both forms of the home directory. The walk compares path strings, and a
|
||||
// symlinked home (e.g. /home -> /var/home) never string-matches the physical
|
||||
// paths a cwd-resolved target produces, which would let the post-boundary walk
|
||||
@@ -664,13 +737,17 @@ export function findDesignRoot(startDir) {
|
||||
if (!boundary && resolveDesignMdPath(dir)) return { dir, hasDesign: true };
|
||||
if (boundary) {
|
||||
// Past the boundary the walk only looks for the monorepo root that owns
|
||||
// the workspace. Monorepo-root before .git, same order as context.mjs:
|
||||
// a workspace root carrying its own .git is still recognized, while a
|
||||
// .git that declares no workspaces is a separate repository and stops
|
||||
// the walk with nothing inherited. The home directory is never an
|
||||
// the workspace path (workspace globs including negations, or marker-only
|
||||
// apps/packages fallback). Monorepo-root before .git, same order as
|
||||
// context.mjs: a workspace root carrying its own .git is still recognized,
|
||||
// while a .git that declares no workspaces is a separate repository and
|
||||
// stops the walk with nothing inherited. The home directory is never an
|
||||
// owning root, same as context.mjs's findMonorepoRoot, which stops at
|
||||
// homeDir before its monorepo check.
|
||||
if (!homeDirs.has(dir) && isMonorepoRoot(dir)) return { dir, hasDesign: !!resolveDesignMdPath(dir) };
|
||||
if (!homeDirs.has(dir) && isMonorepoRoot(dir)) {
|
||||
if (monorepoOwnsPath(dir, boundary.dir)) return { dir, hasDesign: !!resolveDesignMdPath(dir) };
|
||||
return boundary;
|
||||
}
|
||||
if (fs.existsSync(path.join(dir, '.git'))) return boundary;
|
||||
} else if (PROJECT_ROOT_MARKERS.some((marker) => fs.existsSync(path.join(dir, marker)))) {
|
||||
boundary = { dir, hasDesign: false };
|
||||
|
||||
@@ -13,6 +13,8 @@ import path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { findDesignRoot } from '../cli/engine/design-system.mjs';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CLI = path.resolve(__dirname, '../cli/bin/cli.js');
|
||||
|
||||
@@ -66,6 +68,12 @@ function mkPnpmMonorepo({ workspaceDesign = null } = {}) {
|
||||
return { dir, page, webDir: path.join(dir, 'apps/web') };
|
||||
}
|
||||
|
||||
function mkTempRoot(prefix) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
tempRoots.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
after(() => {
|
||||
for (const dir of tempRoots) {
|
||||
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ }
|
||||
@@ -256,4 +264,275 @@ typography:
|
||||
'a symlinked $HOME must still stop the walk before inheriting',
|
||||
);
|
||||
});
|
||||
|
||||
it('CSS module at apps/web/app/page.module.css inherits root DESIGN.md', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-cssmod-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'pnpm-workspace.yaml'), "packages:\n - 'apps/*'\n");
|
||||
fs.mkdirSync(path.join(dir, 'apps/web/app'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'apps/web/package.json'), '{"name":"web"}');
|
||||
const css = path.join(dir, 'apps/web/app/page.module.css');
|
||||
fs.writeFileSync(css, '.c{font-family:Verdana,sans-serif}');
|
||||
|
||||
const findings = runDetect(dir, [css]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, css).some((f) => f.ignoreValue?.toLowerCase() === 'verdana'),
|
||||
'Verdana in a CSS module must be flagged via inherited root DESIGN.md',
|
||||
);
|
||||
});
|
||||
|
||||
it('yarn workspaces object form: workspace inherits root DESIGN.md', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-yarnobj-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
|
||||
name: 'mono',
|
||||
workspaces: { packages: ['packages/*'] },
|
||||
}));
|
||||
fs.mkdirSync(path.join(dir, 'packages/ui'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'packages/ui/package.json'), '{"name":"ui"}');
|
||||
const page = path.join(dir, 'packages/ui/page.html');
|
||||
fs.writeFileSync(page, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [page]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, page).some((f) => f.ignoreValue === 'verdana'),
|
||||
'yarn workspaces object form must inherit root DESIGN.md',
|
||||
);
|
||||
});
|
||||
|
||||
it('nx.json marker root: workspace inherits root DESIGN.md', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-nx-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"mono"}');
|
||||
fs.writeFileSync(path.join(dir, 'nx.json'), '{}');
|
||||
fs.mkdirSync(path.join(dir, 'apps/web'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'apps/web/package.json'), '{"name":"web"}');
|
||||
const page = path.join(dir, 'apps/web/page.html');
|
||||
fs.writeFileSync(page, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [page]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, page).some((f) => f.ignoreValue === 'verdana'),
|
||||
'nx.json marker root must inherit root DESIGN.md',
|
||||
);
|
||||
});
|
||||
|
||||
it('file at monorepo root still flags against root DESIGN.md', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-rootfile-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'pnpm-workspace.yaml'), "packages:\n - 'apps/*'\n");
|
||||
const page = path.join(dir, 'page.html');
|
||||
fs.writeFileSync(page, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [page]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, page).some((f) => f.ignoreValue === 'verdana'),
|
||||
'root-level file must be judged against root DESIGN.md',
|
||||
);
|
||||
});
|
||||
|
||||
it('scan from different cwd still inherits via target path', () => {
|
||||
const { page } = mkPnpmMonorepo();
|
||||
const otherCwd = mkTempRoot('impeccable-detect-mono-othercwd-');
|
||||
|
||||
const findings = runDetect(otherCwd, [page]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, page).some((f) => f.ignoreValue === 'verdana'),
|
||||
'resolution must follow the target path, not process.cwd()',
|
||||
);
|
||||
});
|
||||
|
||||
it('findDesignRoot(apps/web) returns monorepo root with hasDesign true', () => {
|
||||
const { dir, webDir } = mkPnpmMonorepo();
|
||||
const found = findDesignRoot(webDir);
|
||||
assert.equal(found.dir, dir);
|
||||
assert.equal(found.hasDesign, true);
|
||||
});
|
||||
|
||||
it('negated workspace package does not inherit root DESIGN.md (Greptile P1)', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-negated-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
|
||||
name: 'mono',
|
||||
workspaces: ['packages/*', '!packages/excluded'],
|
||||
}));
|
||||
fs.mkdirSync(path.join(dir, 'packages/included'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'packages/included/package.json'), '{"name":"included"}');
|
||||
const includedPage = path.join(dir, 'packages/included/page.html');
|
||||
fs.writeFileSync(includedPage, PAGE_HTML);
|
||||
fs.mkdirSync(path.join(dir, 'packages/excluded'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'packages/excluded/package.json'), '{"name":"excluded"}');
|
||||
const excludedPage = path.join(dir, 'packages/excluded/page.html');
|
||||
fs.writeFileSync(excludedPage, PAGE_HTML);
|
||||
const excludedDir = path.join(dir, 'packages/excluded');
|
||||
|
||||
const findings = runDetect(dir, [includedPage, excludedPage]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, includedPage).some((f) => f.ignoreValue === 'verdana'),
|
||||
'included workspace package must inherit root DESIGN.md',
|
||||
);
|
||||
assert.equal(
|
||||
fontFindingsFor(findings, excludedPage).length,
|
||||
0,
|
||||
'negated workspace package must not inherit root DESIGN.md',
|
||||
);
|
||||
const excludedRoot = findDesignRoot(excludedDir);
|
||||
assert.equal(excludedRoot.dir, excludedDir);
|
||||
assert.equal(excludedRoot.hasDesign, false);
|
||||
});
|
||||
|
||||
it('stray nested package outside globs does not inherit root DESIGN.md', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-stray-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'pnpm-workspace.yaml'), "packages:\n - 'apps/*'\n");
|
||||
fs.mkdirSync(path.join(dir, 'apps/web'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'apps/web/package.json'), '{"name":"web"}');
|
||||
const webPage = path.join(dir, 'apps/web/page.html');
|
||||
fs.writeFileSync(webPage, PAGE_HTML);
|
||||
fs.mkdirSync(path.join(dir, 'vendor/tool'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'vendor/tool/package.json'), '{"name":"tool"}');
|
||||
const vendorPage = path.join(dir, 'vendor/tool/page.html');
|
||||
fs.writeFileSync(vendorPage, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [webPage, vendorPage]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, webPage).some((f) => f.ignoreValue === 'verdana'),
|
||||
'apps/web must inherit root DESIGN.md',
|
||||
);
|
||||
assert.equal(
|
||||
fontFindingsFor(findings, vendorPage).length,
|
||||
0,
|
||||
'vendor/tool outside globs must not inherit root DESIGN.md',
|
||||
);
|
||||
});
|
||||
|
||||
it('non-monorepo nested package.json does not inherit root DESIGN.md', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-nestedpkg-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"root"}');
|
||||
fs.mkdirSync(path.join(dir, '.git'), { recursive: true });
|
||||
fs.mkdirSync(path.join(dir, 'packages/nested'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'packages/nested/package.json'), '{"name":"nested"}');
|
||||
const nestedPage = path.join(dir, 'packages/nested/page.html');
|
||||
fs.writeFileSync(nestedPage, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [nestedPage]);
|
||||
assert.equal(
|
||||
fontFindingsFor(findings, nestedPage).length,
|
||||
0,
|
||||
'nested package.json in a non-monorepo must not inherit root DESIGN.md',
|
||||
);
|
||||
});
|
||||
|
||||
it('non-monorepo without DESIGN.md: no design-system-font findings', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-nodesign-');
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"root"}');
|
||||
const page = path.join(dir, 'page.html');
|
||||
fs.writeFileSync(page, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [page]);
|
||||
assert.equal(
|
||||
fontFindingsFor(findings, page).length,
|
||||
0,
|
||||
'no DESIGN.md means no design-system-font findings',
|
||||
);
|
||||
});
|
||||
|
||||
it('single-package repo: src/page.html inherits root DESIGN.md', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-single-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"app"}');
|
||||
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
||||
const page = path.join(dir, 'src/page.html');
|
||||
fs.writeFileSync(page, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [page]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, page).some((f) => f.ignoreValue === 'verdana'),
|
||||
'src/ without its own package.json must inherit project DESIGN.md',
|
||||
);
|
||||
});
|
||||
|
||||
it('DESIGN.md in docs/ fallback still flags in single-package repo', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-docsfb-');
|
||||
fs.mkdirSync(path.join(dir, 'docs'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'docs/DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"app"}');
|
||||
const page = path.join(dir, 'src/page.html');
|
||||
fs.mkdirSync(path.dirname(page), { recursive: true });
|
||||
fs.writeFileSync(page, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [page]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, page).some((f) => f.ignoreValue === 'verdana'),
|
||||
'docs/DESIGN.md fallback must apply to nested files',
|
||||
);
|
||||
});
|
||||
|
||||
it('pnpm !**/test/** does not smash sibling workspaces', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-globstar-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'pnpm-workspace.yaml'), [
|
||||
'packages:',
|
||||
" - 'packages/*'",
|
||||
" - 'components/**'",
|
||||
" - '!**/test/**'",
|
||||
'',
|
||||
].join('\n'));
|
||||
fs.mkdirSync(path.join(dir, 'packages/ui'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'packages/ui/package.json'), '{"name":"ui"}');
|
||||
const uiPage = path.join(dir, 'packages/ui/page.html');
|
||||
fs.writeFileSync(uiPage, PAGE_HTML);
|
||||
fs.mkdirSync(path.join(dir, 'components/button'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'components/button/package.json'), '{"name":"button"}');
|
||||
const buttonPage = path.join(dir, 'components/button/page.html');
|
||||
fs.writeFileSync(buttonPage, PAGE_HTML);
|
||||
fs.mkdirSync(path.join(dir, 'packages/ui/test/fixture'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'packages/ui/test/fixture/package.json'), '{"name":"fixture"}');
|
||||
const testPage = path.join(dir, 'packages/ui/test/fixture/page.html');
|
||||
fs.writeFileSync(testPage, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [uiPage, buttonPage, testPage]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, uiPage).some((f) => f.ignoreValue === 'verdana'),
|
||||
'packages/ui must still inherit when a globstar test exclusion is present',
|
||||
);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, buttonPage).some((f) => f.ignoreValue === 'verdana'),
|
||||
'components/** must still inherit when a globstar test exclusion is present',
|
||||
);
|
||||
assert.equal(
|
||||
fontFindingsFor(findings, testPage).length,
|
||||
0,
|
||||
'packages/ui/test/fixture must not inherit under !**/test/**',
|
||||
);
|
||||
});
|
||||
|
||||
it('workspaces ["*"] owns only direct children, not vendor/tool', () => {
|
||||
const dir = mkTempRoot('impeccable-detect-mono-star-');
|
||||
fs.writeFileSync(path.join(dir, 'DESIGN.md'), DESIGN_MD);
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
|
||||
name: 'mono',
|
||||
workspaces: ['*'],
|
||||
}));
|
||||
fs.mkdirSync(path.join(dir, 'web'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'web/package.json'), '{"name":"web"}');
|
||||
const webPage = path.join(dir, 'web/page.html');
|
||||
fs.writeFileSync(webPage, PAGE_HTML);
|
||||
fs.mkdirSync(path.join(dir, 'vendor/tool'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'vendor/tool/package.json'), '{"name":"tool"}');
|
||||
const vendorPage = path.join(dir, 'vendor/tool/page.html');
|
||||
fs.writeFileSync(vendorPage, PAGE_HTML);
|
||||
|
||||
const findings = runDetect(dir, [webPage, vendorPage]);
|
||||
assert.ok(
|
||||
fontFindingsFor(findings, webPage).some((f) => f.ignoreValue === 'verdana'),
|
||||
'direct-child workspace under * must inherit root DESIGN.md',
|
||||
);
|
||||
assert.equal(
|
||||
fontFindingsFor(findings, vendorPage).length,
|
||||
0,
|
||||
'vendor/tool is not a direct child of * and must not inherit',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user