Skip to content

Commit 478b4e2

Browse files
Validate file registry paths during validate() for --emit none (#3078)
* Validate file registry paths during `validate()` for `--emit none` When `--emit none` is used with `treatWarningsAsErrors`, warnings about relative links pointing to directories were silently suppressed because the only `isFile()` check lived in `AssetsPlugin.onRenderEnd()`, which is skipped entirely when rendering is disabled. This adds a `validateFilePaths()` validation pass that runs during `Application.validate()`, gated on the existing `validation.invalidPath` option. It iterates all registered file paths that lack a reflection association (preserving packages-mode directory links which map to reflections) and emits `validationWarning()` for non-file entries. Because `validate()` runs before the `--emit none` gate in `cli.ts`, directory-pointing relative links now produce warnings regardless of emit mode. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 66d0935 commit 478b4e2

5 files changed

Lines changed: 81 additions & 4 deletions

File tree

src/lib/application.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { fileURLToPath } from "url";
4141
import { createRequire } from "module";
4242
import { Outputs } from "./output/output.js";
4343
import { validateMergeModuleWith } from "./validation/unusedMergeModuleWith.js";
44+
import { validateFilePaths } from "./validation/filePaths.js";
4445
import { diagnostic, diagnostics } from "./utils/loggers.js";
4546
import { ValidatingFileRegistry } from "./utils/ValidatingFileRegistry.js";
4647
import { Internationalization } from "./internationalization/internationalization.js";
@@ -724,6 +725,10 @@ export class Application extends AbstractComponent<
724725
validateMergeModuleWith(project, this.logger);
725726
}
726727

728+
if (checks.invalidPath) {
729+
validateFilePaths(project, this.logger);
730+
}
731+
727732
this.trigger(Application.EVENT_VALIDATE_PROJECT, project);
728733

729734
this.logger.verbose(`Validation took ${Date.now() - start}ms`);

src/lib/models/FileRegistry.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ export class FileRegistry {
121121
return this.names.get(id);
122122
}
123123

124+
/**
125+
* Iterate over all registered media file paths, yielding entries
126+
* that do NOT have an associated reflection.
127+
*/
128+
getMediaPaths(): Iterable<NormalizedPath> {
129+
const result: NormalizedPath[] = [];
130+
for (const [id, path] of this.mediaToPath.entries()) {
131+
if (!this.mediaToReflection.has(id)) {
132+
result.push(path);
133+
}
134+
}
135+
return result;
136+
}
137+
124138
getNameToAbsoluteMap(): ReadonlyMap<string, string> {
125139
const result = new Map<string, string>();
126140
for (const [id, name] of this.names.entries()) {

src/lib/output/plugins/AssetsPlugin.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,6 @@ export class AssetsPlugin extends RendererComponent {
125125
for (const [fileName, absolute] of toCopy.entries()) {
126126
if (isFile(absolute)) {
127127
copySync(absolute, join(media, fileName));
128-
} else {
129-
this.application.logger.warn(
130-
i18n.relative_path_0_is_not_a_file_and_will_not_be_copied_to_output(absolute),
131-
);
132128
}
133129
}
134130
}

src/lib/validation/filePaths.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { ProjectReflection } from "../models/index.js";
2+
import { i18n, type Logger } from "#utils";
3+
import { isFile } from "../utils/fs.js";
4+
5+
export function validateFilePaths(
6+
project: ProjectReflection,
7+
logger: Logger,
8+
): void {
9+
for (const absolute of project.files.getMediaPaths()) {
10+
if (!isFile(absolute)) {
11+
logger.validationWarning(
12+
i18n.relative_path_0_is_not_a_file_and_will_not_be_copied_to_output(absolute),
13+
);
14+
}
15+
}
16+
}

src/test/validation.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { ok } from "assert";
22
import { join } from "path";
33
import { validateDocumentation } from "../lib/validation/documentation.js";
44
import { validateExports } from "../lib/validation/exports.js";
5+
import { validateFilePaths } from "../lib/validation/filePaths.js";
56
import { getConverter2App, getConverter2Program, getConverter2Project } from "./programs.js";
67
import { TestLogger } from "./TestLogger.js";
78
import { fileURLToPath } from "url";
89
import { validateMergeModuleWith } from "../lib/validation/unusedMergeModuleWith.js";
10+
import { normalizePath } from "#node-utils";
911

1012
function convertValidationFile(...files: [string, ...string[]]) {
1113
return getConverter2Project(files, "validation");
@@ -233,3 +235,47 @@ describe("validateMergeModuleWith", () => {
233235
);
234236
});
235237
});
238+
239+
describe("validateFilePaths", () => {
240+
it("Should warn if a registered path is a directory", () => {
241+
const project = convertValidationFile("variable.ts");
242+
// Register a directory path (the converter2 directory itself) without a reflection association
243+
const dirPath = normalizePath(
244+
join(fileURLToPath(import.meta.url), "../converter2"),
245+
);
246+
project.files.registerAbsolute(dirPath);
247+
248+
const logger = new TestLogger();
249+
validateFilePaths(project, logger);
250+
251+
logger.expectMessage(
252+
`warn: The relative path ${dirPath} is not a file and will not be copied to the output directory`,
253+
);
254+
});
255+
256+
it("Should not warn for registered files", () => {
257+
const project = convertValidationFile("variable.ts");
258+
const filePath = normalizePath(
259+
join(fileURLToPath(import.meta.url), "../converter2/validation/variable.ts"),
260+
);
261+
project.files.registerAbsolute(filePath);
262+
263+
const logger = new TestLogger();
264+
validateFilePaths(project, logger);
265+
266+
logger.expectNoOtherMessages();
267+
});
268+
269+
it("Should not warn for directories with a reflection association", () => {
270+
const project = convertValidationFile("variable.ts");
271+
const dirPath = normalizePath(
272+
join(fileURLToPath(import.meta.url), "../converter2"),
273+
);
274+
project.files.registerReflectionPath(dirPath, project);
275+
276+
const logger = new TestLogger();
277+
validateFilePaths(project, logger);
278+
279+
logger.expectNoOtherMessages();
280+
});
281+
});

0 commit comments

Comments
 (0)