Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/theming-targets-guard-nested.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astryxdesign/core': patch
---

[fix] `themingTargets.test.ts` now discovers component sources at any depth under `src`, not only in a top-level directory. Sources nested a level down — `Table/plugins/<name>/` — were silently exempt from the guard, which is the same drift #3741 was filed to prevent. Nothing was failing (no nested source rendered a `themeProps()` class before this release), so this closes the hole rather than fixing a live break: the guard goes from 294 to 302 assertions.
@freddymeta
46 changes: 39 additions & 7 deletions packages/core/src/theme/themingTargets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

import {describe, it, expect} from 'vitest';
import {readdirSync, readFileSync} from 'node:fs';
import {join, relative} from 'node:path';
import {basename, join, relative} from 'node:path';
import ts from 'typescript';
import {stableClassName} from '../naming';

Expand Down Expand Up @@ -377,17 +377,42 @@ function docFilesDocumenting(
return matches;
}

/**
* Every directory under `srcDir`, at any depth, relative to `srcDir`.
*
* Not just the top level: a component's sources are not always its own direct
* children — Table's plugins render from `Table/plugins/<name>/`. Scanning only
* the top level exempted every one of those from this guard, which is the same
* silent-exemption shape #3741 was filed about.
*
* Takes `srcDir` rather than closing over a module constant so it serves both
* packages, which is what the Lab enrollment above needs.
*/
function sourceDirs(srcDir: string): string[] {
const out: string[] = [];
const walk = (rel: string): void => {
for (const entry of readdirSync(join(srcDir, rel), {
withFileTypes: true,
})) {
if (entry.isDirectory()) {
const child = rel === '' ? entry.name : join(rel, entry.name);
out.push(child);
walk(child);
}
}
};
walk('');
return out;
}

function discoverComponents(
srcDir: string,
packageName: ComponentInfo['packageName'],
requiredDirs: ReadonlySet<string> = new Set(),
): ComponentInfo[] {
const results: ComponentInfo[] = [];
const dirs = readdirSync(srcDir, {withFileTypes: true})
.filter(d => d.isDirectory())
.map(d => d.name);

for (const dir of dirs) {
for (const dir of sourceDirs(srcDir)) {
const dirPath = join(srcDir, dir);
const dirEntries = readdirSync(dirPath);

Expand Down Expand Up @@ -418,9 +443,16 @@ function discoverComponents(
// Both paths match the on-disk listing rather than existsSync: on
// case-insensitive filesystems existsSync would match a differently-cased
// doc file that CI never checks. (Same guard as derivedVarRegistry.test.ts.)
const docFiles = dirEntries.includes(`${dir}.doc.mjs`)
? [join(dirPath, `${dir}.doc.mjs`)]
// `basename`, not `dir`: a nested source dir is a path
// (`Table/plugins/foo`), and its own doc file is named for the last
// segment.
const ownDoc = `${basename(dir)}.doc.mjs`;
const docFiles = dirEntries.includes(ownDoc)
? [join(dirPath, ownDoc)]
: docFilesDocumenting(srcDir, new Set(sites.map(s => s.className)));
if (docFiles.length === 0) {
continue;
}

const docBlocks: ComponentInfo['docBlocks'] = [];
for (const docFile of docFiles) {
Expand Down
Loading