diff --git a/src/lib/application.ts b/src/lib/application.ts index 75b5cd214..8bd1302be 100644 --- a/src/lib/application.ts +++ b/src/lib/application.ts @@ -41,6 +41,7 @@ import { fileURLToPath } from "url"; import { createRequire } from "module"; import { Outputs } from "./output/output.js"; import { validateMergeModuleWith } from "./validation/unusedMergeModuleWith.js"; +import { validateFilePaths } from "./validation/filePaths.js"; import { diagnostic, diagnostics } from "./utils/loggers.js"; import { ValidatingFileRegistry } from "./utils/ValidatingFileRegistry.js"; import { Internationalization } from "./internationalization/internationalization.js"; @@ -703,6 +704,10 @@ export class Application extends AbstractComponent< validateMergeModuleWith(project, this.logger); } + if (checks.invalidPath) { + validateFilePaths(project, this.logger); + } + this.trigger(Application.EVENT_VALIDATE_PROJECT, project); this.logger.verbose(`Validation took ${Date.now() - start}ms`); diff --git a/src/lib/models/FileRegistry.ts b/src/lib/models/FileRegistry.ts index 6cda93a04..96f646efb 100644 --- a/src/lib/models/FileRegistry.ts +++ b/src/lib/models/FileRegistry.ts @@ -121,6 +121,20 @@ export class FileRegistry { return this.names.get(id); } + /** + * Iterate over all registered media file paths, yielding entries + * that do NOT have an associated reflection. + */ + getMediaPaths(): Iterable { + const result: NormalizedPath[] = []; + for (const [id, path] of this.mediaToPath.entries()) { + if (!this.mediaToReflection.has(id)) { + result.push(path); + } + } + return result; + } + getNameToAbsoluteMap(): ReadonlyMap { const result = new Map(); for (const [id, name] of this.names.entries()) { diff --git a/src/lib/output/plugins/AssetsPlugin.ts b/src/lib/output/plugins/AssetsPlugin.ts index 71b53d35b..0422907a3 100644 --- a/src/lib/output/plugins/AssetsPlugin.ts +++ b/src/lib/output/plugins/AssetsPlugin.ts @@ -125,10 +125,6 @@ export class AssetsPlugin extends RendererComponent { for (const [fileName, absolute] of toCopy.entries()) { if (isFile(absolute)) { copySync(absolute, join(media, fileName)); - } else { - this.application.logger.warn( - i18n.relative_path_0_is_not_a_file_and_will_not_be_copied_to_output(absolute), - ); } } } diff --git a/src/lib/validation/filePaths.ts b/src/lib/validation/filePaths.ts new file mode 100644 index 000000000..f102513fa --- /dev/null +++ b/src/lib/validation/filePaths.ts @@ -0,0 +1,16 @@ +import type { ProjectReflection } from "../models/index.js"; +import { i18n, type Logger } from "#utils"; +import { isFile } from "../utils/fs.js"; + +export function validateFilePaths( + project: ProjectReflection, + logger: Logger, +): void { + for (const absolute of project.files.getMediaPaths()) { + if (!isFile(absolute)) { + logger.validationWarning( + i18n.relative_path_0_is_not_a_file_and_will_not_be_copied_to_output(absolute), + ); + } + } +} diff --git a/src/test/validation.test.ts b/src/test/validation.test.ts index 905672c25..5f1d29f5a 100644 --- a/src/test/validation.test.ts +++ b/src/test/validation.test.ts @@ -2,10 +2,12 @@ import { ok } from "assert"; import { join } from "path"; import { validateDocumentation } from "../lib/validation/documentation.js"; import { validateExports } from "../lib/validation/exports.js"; +import { validateFilePaths } from "../lib/validation/filePaths.js"; import { getConverter2App, getConverter2Program, getConverter2Project } from "./programs.js"; import { TestLogger } from "./TestLogger.js"; import { fileURLToPath } from "url"; import { validateMergeModuleWith } from "../lib/validation/unusedMergeModuleWith.js"; +import { normalizePath } from "#node-utils"; function convertValidationFile(...files: [string, ...string[]]) { return getConverter2Project(files, "validation"); @@ -233,3 +235,47 @@ describe("validateMergeModuleWith", () => { ); }); }); + +describe("validateFilePaths", () => { + it("Should warn if a registered path is a directory", () => { + const project = convertValidationFile("variable.ts"); + // Register a directory path (the converter2 directory itself) without a reflection association + const dirPath = normalizePath( + join(fileURLToPath(import.meta.url), "../converter2"), + ); + project.files.registerAbsolute(dirPath); + + const logger = new TestLogger(); + validateFilePaths(project, logger); + + logger.expectMessage( + `warn: The relative path ${dirPath} is not a file and will not be copied to the output directory`, + ); + }); + + it("Should not warn for registered files", () => { + const project = convertValidationFile("variable.ts"); + const filePath = normalizePath( + join(fileURLToPath(import.meta.url), "../converter2/validation/variable.ts"), + ); + project.files.registerAbsolute(filePath); + + const logger = new TestLogger(); + validateFilePaths(project, logger); + + logger.expectNoOtherMessages(); + }); + + it("Should not warn for directories with a reflection association", () => { + const project = convertValidationFile("variable.ts"); + const dirPath = normalizePath( + join(fileURLToPath(import.meta.url), "../converter2"), + ); + project.files.registerReflectionPath(dirPath, project); + + const logger = new TestLogger(); + validateFilePaths(project, logger); + + logger.expectNoOtherMessages(); + }); +});