Skip to content

Commit 209acb5

Browse files
committed
Improve error reporting for Windows path separators
Resolves #2938
1 parent 5824623 commit 209acb5

File tree

9 files changed

+51
-27
lines changed

9 files changed

+51
-27
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ title: Changelog
1010
- `@link` links to the current page are now rendered, #2934.
1111
- `@includeCode` now supports regions in TypeScript files with `.mts` and `.cts` file extensions, #2935.
1212
- Aliased symbols (re-exports) are now resolved before checking if they are excluded/external, #2937.
13+
- Improved error reporting when paths including Windows separators are provided as globs, #2938.
1314

1415
## v0.28.2 (2025-04-07)
1516

src/lib/cli.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async function main() {
2626
new td.TypeDocReader(),
2727
new td.PackageJsonReader(),
2828
new td.TSConfigReader(),
29-
new td.ArgumentsReader(300),
29+
new td.ArgumentsReader(300).ignoreErrors(),
3030
]);
3131

3232
const exitCode = await run(app);

src/lib/internationalization/locales/en.cts

+2-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ export = {
200200
use_expand_or_glob_for_files_in_dir:
201201
`If you wanted to include files inside this directory, set --entryPointStrategy to expand or specify a glob`,
202202
glob_0_did_not_match_any_files: `The glob {0} did not match any files`,
203-
glob_should_use_posix_slash: `Try replacing Windows path separators (\\) with posix path separators (/)`,
204203
entry_point_0_did_not_match_any_files_after_exclude:
205204
`The glob {0} did not match any files after applying exclude patterns`,
206205
entry_point_0_did_not_exist: `Provided entry point {0} does not exist`,
@@ -218,6 +217,8 @@ export = {
218217
circular_reference_extends_0: `Circular reference encountered for "extends" field of {0}`,
219218
failed_resolve_0_to_file_in_1: `Failed to resolve {0} to a file in {1}`,
220219

220+
glob_0_should_use_posix_slash:
221+
`The glob "{0}" escapes a non-special character. Glob inputs to TypeDoc may not use Windows path separators (\\), try replacing with posix path separators (/)`,
221222
option_0_can_only_be_specified_by_config_file: `The '{0}' option can only be specified via a config file`,
222223
option_0_expected_a_value_but_none_provided: `--{0} expected a value, but none was given as an argument`,
223224
unknown_option_0_may_have_meant_1: `Unknown option: {0}, you may have meant:\n\t{1}`,

src/lib/internationalization/locales/ja.cts

-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ export = localeUtils.buildIncompleteTranslation({
138138
use_expand_or_glob_for_files_in_dir:
139139
"このディレクトリ内のファイルを含める場合は、--entryPointStrategyを設定して展開するか、globを指定します。",
140140
glob_0_did_not_match_any_files: "グロブ {0} はどのファイルにも一致しませんでした",
141-
// glob_should_use_posix_slash
142141
entry_point_0_did_not_match_any_files_after_exclude:
143142
"除外パターンを適用した後、グロブ {0} はどのファイルにも一致しませんでした",
144143
entry_point_0_did_not_exist: "指定されたエントリ ポイント {0} は存在しません",

src/lib/internationalization/locales/zh.cts

-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ export = localeUtils.buildIncompleteTranslation({
174174
failed_to_resolve_0_to_ts_path: "无法将 package.json 中的入口点 {0} 解析至 TypeScript 源文件",
175175
use_expand_or_glob_for_files_in_dir: "如果要包含此目录中的文件,请设置 --entryPointStrategy 以展开或指定 glob",
176176
glob_0_did_not_match_any_files: "glob {0} 与任何文件均不匹配",
177-
glob_should_use_posix_slash: `请将 Windows 路径分隔符(\\)替换为 POSIX 路径分隔符(/)`,
178177
entry_point_0_did_not_match_any_files_after_exclude: "应用排除模式后,glob {0} 没有匹配任何文件",
179178
entry_point_0_did_not_exist: "提供的入口点 {0} 不存在",
180179
entry_point_0_did_not_match_any_packages: "入口点 glob {0} 与任何包含 package.json 的目录不匹配",

src/lib/utils/entry-point.ts

-3
Original file line numberDiff line numberDiff line change
@@ -372,9 +372,6 @@ function expandGlobs(globs: GlobString[], exclude: GlobString[], logger: Logger)
372372
logger.warn(
373373
i18n.glob_0_did_not_match_any_files(entry),
374374
);
375-
if (entry.includes("\\") && !entry.includes("/")) {
376-
logger.info(i18n.glob_should_use_posix_slash());
377-
}
378375
} else if (filtered.length === 0) {
379376
logger.warn(
380377
i18n.entry_point_0_did_not_match_any_files_after_exclude(

src/lib/utils/options/declaration.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,19 @@ const converters: {
769769
return resolved;
770770
},
771771
[ParameterType.GlobArray](value, option, configPath) {
772-
const toGlobString = (v: unknown) => createGlobString(configPath, String(v));
772+
const toGlobString = (v: unknown) => {
773+
const s = String(v);
774+
775+
// If the string tries to escape a character which isn't a special
776+
// glob character, the user probably provided a Windows style path
777+
// by accident due to shell completion, tell them to either remove
778+
// the useless escape or switch to Unix path separators.
779+
if (/\\[^?*()[\]\\{}]/.test(s)) {
780+
throw new Error(i18n.glob_0_should_use_posix_slash(s));
781+
}
782+
783+
return createGlobString(configPath, s);
784+
};
773785
const globs = Array.isArray(value) ? value.map(toGlobString) : [toGlobString(value)];
774786
option.validate?.(globs);
775787
return globs;

src/lib/utils/options/readers/arguments.ts

+31-17
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ export class ArgumentsReader implements OptionsReader {
1818
readonly order: number;
1919
readonly supportsPackages = false;
2020
private args: string[];
21+
private skipErrorReporting = false;
2122

2223
constructor(priority: number, args = process.argv.slice(2)) {
2324
this.order = priority;
2425
this.args = args;
2526
}
2627

28+
ignoreErrors() {
29+
this.skipErrorReporting = true;
30+
return this;
31+
}
32+
2733
read(container: Options, logger: Logger): void {
2834
// Make container's type more lax, we do the appropriate checks manually.
2935
const options = container as Options & {
@@ -38,7 +44,9 @@ export class ArgumentsReader implements OptionsReader {
3844
options.setValue(name, value);
3945
} catch (err) {
4046
ok(err instanceof Error);
41-
logger.error(err.message as TranslatedString);
47+
if (!this.skipErrorReporting) {
48+
logger.error(err.message as TranslatedString);
49+
}
4250
}
4351
};
4452

@@ -50,11 +58,13 @@ export class ArgumentsReader implements OptionsReader {
5058

5159
if (decl) {
5260
if (decl.configFileOnly) {
53-
logger.error(
54-
i18n.option_0_can_only_be_specified_by_config_file(
55-
decl.name,
56-
),
57-
);
61+
if (!this.skipErrorReporting) {
62+
logger.error(
63+
i18n.option_0_can_only_be_specified_by_config_file(
64+
decl.name,
65+
),
66+
);
67+
}
5868
continue;
5969
}
6070

@@ -81,11 +91,13 @@ export class ArgumentsReader implements OptionsReader {
8191
} else {
8292
if (index === this.args.length) {
8393
// Only boolean values have optional values.
84-
logger.warn(
85-
i18n.option_0_expected_a_value_but_none_provided(
86-
decl.name,
87-
),
88-
);
94+
if (!this.skipErrorReporting) {
95+
logger.warn(
96+
i18n.option_0_expected_a_value_but_none_provided(
97+
decl.name,
98+
),
99+
);
100+
}
89101
}
90102
trySet(decl.name, this.args[index]);
91103
}
@@ -115,12 +127,14 @@ export class ArgumentsReader implements OptionsReader {
115127
}
116128
}
117129

118-
logger.error(
119-
i18n.unknown_option_0_may_have_meant_1(
120-
name,
121-
options.getSimilarOptions(name).join("\n\t"),
122-
),
123-
);
130+
if (!this.skipErrorReporting) {
131+
logger.error(
132+
i18n.unknown_option_0_may_have_meant_1(
133+
name,
134+
options.getSimilarOptions(name).join("\n\t"),
135+
),
136+
);
137+
}
124138
index++;
125139
}
126140
}

src/test/programs.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { createAppForTesting } from "../lib/application.js";
1515
import { existsSync } from "fs";
1616
import { clearCommentCache } from "../lib/converter/comments/index.js";
1717
import { diagnostics } from "../lib/utils/loggers.js";
18-
import { readFile } from "#node-utils";
18+
import { normalizePath, readFile } from "#node-utils";
1919

2020
let converterApp: Application | undefined;
2121
let converterProgram: ts.Program | undefined;
@@ -163,7 +163,8 @@ export function getConverter2Project(entries: string[], folder: string) {
163163
join(base, folder, entry),
164164
].find(existsSync)
165165
)
166-
.filter((x) => x !== undefined);
166+
.filter((x) => x !== undefined)
167+
.map(normalizePath);
167168

168169
const files = entryPoints.map((e) => program.getSourceFile(e));
169170
for (const [index, file] of files.entries()) {

0 commit comments

Comments
 (0)