Skip to content

Commit 92f3f1c

Browse files
committed
Allow files to be included by *.json pattern in include of tsconfig
Fixes microsoft#25636
1 parent 6365eb0 commit 92f3f1c

File tree

5 files changed

+73
-6
lines changed

5 files changed

+73
-6
lines changed

src/compiler/commandLineParser.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2509,11 +2509,16 @@ namespace ts {
25092509
// via wildcard, and to handle extension priority.
25102510
const wildcardFileMap = createMap<string>();
25112511

2512+
// Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a
2513+
// file map with a possibly case insensitive key. We use this map to store paths matched
2514+
// via wildcard of *.json kind
2515+
const wildCardJsonFileMap = createMap<string>();
25122516
const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec;
25132517

25142518
// Rather than requery this for each file and filespec, we query the supported extensions
25152519
// once and store it on the expansion context.
25162520
const supportedExtensions = getSupportedExtensions(options, extraFileExtensions);
2521+
const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions);
25172522

25182523
// Literal files are always included verbatim. An "include" or "exclude" specification cannot
25192524
// remove a literal file.
@@ -2524,8 +2529,25 @@ namespace ts {
25242529
}
25252530
}
25262531

2532+
let jsonOnlyIncludeRegexes: ReadonlyArray<RegExp> | undefined;
25272533
if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) {
2528-
for (const file of host.readDirectory(basePath, supportedExtensions, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) {
2534+
for (const file of host.readDirectory(basePath, supportedExtensionsWithJsonIfResolveJsonModule, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) {
2535+
if (fileExtensionIs(file, Extension.Json)) {
2536+
// Valid only if *.json specified
2537+
if (!jsonOnlyIncludeRegexes) {
2538+
const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json));
2539+
const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`);
2540+
jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray;
2541+
}
2542+
const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file));
2543+
if (includeIndex !== -1) {
2544+
const key = keyMapper(file);
2545+
if (!literalFileMap.has(key) && !wildCardJsonFileMap.has(key)) {
2546+
wildCardJsonFileMap.set(key, file);
2547+
}
2548+
}
2549+
continue;
2550+
}
25292551
// If we have already included a literal or wildcard path with a
25302552
// higher priority extension, we should skip this file.
25312553
//
@@ -2553,7 +2575,7 @@ namespace ts {
25532575
const wildcardFiles = arrayFrom(wildcardFileMap.values());
25542576

25552577
return {
2556-
fileNames: literalFiles.concat(wildcardFiles),
2578+
fileNames: literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())),
25572579
wildcardDirectories,
25582580
spec
25592581
};

src/compiler/program.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ namespace ts {
610610
const programDiagnostics = createDiagnosticCollection();
611611
const currentDirectory = host.getCurrentDirectory();
612612
const supportedExtensions = getSupportedExtensions(options);
613-
const supportedExtensionsWithJsonIfResolveJsonModule = options.resolveJsonModule ? [...supportedExtensions, Extension.Json] : undefined;
613+
const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions);
614614

615615
// Map storing if there is emit blocking diagnostics for given input
616616
const hasEmitBlockingDiagnostics = createMap<boolean>();
@@ -1965,7 +1965,7 @@ namespace ts {
19651965
refFile?: SourceFile): SourceFile | undefined {
19661966

19671967
if (hasExtension(fileName)) {
1968-
if (!options.allowNonTsExtensions && !forEach(supportedExtensionsWithJsonIfResolveJsonModule || supportedExtensions, extension => fileExtensionIs(host.getCanonicalFileName(fileName), extension))) {
1968+
if (!options.allowNonTsExtensions && !forEach(supportedExtensionsWithJsonIfResolveJsonModule, extension => fileExtensionIs(host.getCanonicalFileName(fileName), extension))) {
19691969
if (fail) fail(Diagnostics.File_0_has_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + supportedExtensions.join("', '") + "'");
19701970
return undefined;
19711971
}

src/compiler/utilities.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7738,7 +7738,7 @@ namespace ts {
77387738
return `^(${pattern})${terminator}`;
77397739
}
77407740

7741-
function getRegularExpressionsForWildcards(specs: ReadonlyArray<string> | undefined, basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined {
7741+
export function getRegularExpressionsForWildcards(specs: ReadonlyArray<string> | undefined, basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined {
77427742
if (specs === undefined || specs.length === 0) {
77437743
return undefined;
77447744
}
@@ -8003,11 +8003,13 @@ namespace ts {
80038003
* List of supported extensions in order of file resolution precedence.
80048004
*/
80058005
export const supportedTSExtensions: ReadonlyArray<Extension> = [Extension.Ts, Extension.Tsx, Extension.Dts];
8006+
export const supportedTSExtensionsWithJson: ReadonlyArray<Extension> = [Extension.Ts, Extension.Tsx, Extension.Dts, Extension.Json];
80068007
/** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */
80078008
export const supportedTSExtensionsForExtractExtension: ReadonlyArray<Extension> = [Extension.Dts, Extension.Ts, Extension.Tsx];
80088009
export const supportedJSExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx];
80098010
export const supportedJSAndJsonExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx, Extension.Json];
80108011
const allSupportedExtensions: ReadonlyArray<Extension> = [...supportedTSExtensions, ...supportedJSExtensions];
8012+
const allSupportedExtensionsWithJson: ReadonlyArray<Extension> = [...supportedTSExtensions, ...supportedJSExtensions, Extension.Json];
80118013

80128014
export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: ReadonlyArray<FileExtensionInfo>): ReadonlyArray<string> {
80138015
const needJsExtensions = options && options.allowJs;
@@ -8024,6 +8026,13 @@ namespace ts {
80248026
return deduplicate<string>(extensions, equateStringsCaseSensitive, compareStringsCaseSensitive);
80258027
}
80268028

8029+
export function getSuppoertedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: ReadonlyArray<string>): ReadonlyArray<string> {
8030+
if (!options || !options.resolveJsonModule) { return supportedExtensions; }
8031+
if (supportedExtensions === allSupportedExtensions) { return allSupportedExtensionsWithJson; }
8032+
if (supportedExtensions === supportedTSExtensions) { return supportedTSExtensionsWithJson; }
8033+
return [...supportedExtensions, Extension.Json];
8034+
}
8035+
80278036
function isJSLike(scriptKind: ScriptKind | undefined): boolean {
80288037
return scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSX;
80298038
}
@@ -8043,7 +8052,8 @@ namespace ts {
80438052
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, extraFileExtensions?: ReadonlyArray<FileExtensionInfo>) {
80448053
if (!fileName) { return false; }
80458054

8046-
for (const extension of getSupportedExtensions(compilerOptions, extraFileExtensions)) {
8055+
const supportedExtensions = getSupportedExtensions(compilerOptions, extraFileExtensions);
8056+
for (const extension of getSuppoertedExtensionsWithJsonIfResolveJsonModule(compilerOptions, supportedExtensions)) {
80478057
if (fileExtensionIs(fileName, extension)) {
80488058
return true;
80498059
}

src/testRunner/unittests/tsbuild.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ export class cNew {}`);
276276

277277
function verifyProjectWithResolveJsonModule(configFile: string, ...expectedDiagnosticMessages: DiagnosticMessage[]) {
278278
const fs = projFs.shadow();
279+
verifyProjectWithResolveJsonModuleWithFs(fs, configFile, allExpectedOutputs, ...expectedDiagnosticMessages);
280+
}
281+
282+
function verifyProjectWithResolveJsonModuleWithFs(fs: vfs.FileSystem, configFile: string, allExpectedOutputs: ReadonlyArray<string>, ...expectedDiagnosticMessages: DiagnosticMessage[]) {
279283
const host = new fakes.SolutionBuilderHost(fs);
280284
const builder = createSolutionBuilder(host, [configFile], { dry: false, force: false, verbose: false });
281285
builder.buildAllProjects();
@@ -292,6 +296,21 @@ export class cNew {}`);
292296
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withInclude.json", Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern);
293297
});
294298

299+
it("with resolveJsonModule and include of *.json along with other include", () => {
300+
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeOfJson.json");
301+
});
302+
303+
it("with resolveJsonModule and include of *.json along with other include and file name matches ts file", () => {
304+
const fs = projFs.shadow();
305+
fs.rimrafSync("/src/tests/src/hello.json");
306+
fs.writeFileSync("/src/tests/src/index.json", JSON.stringify({ hello: "world" }));
307+
fs.writeFileSync("/src/tests/src/index.ts", `import hello from "./index.json"
308+
309+
export default hello.hello`);
310+
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/index.json"];
311+
verifyProjectWithResolveJsonModuleWithFs(fs, "/src/tests/tsconfig_withIncludeOfJson.json", allExpectedOutputs);
312+
});
313+
295314
it("with resolveJsonModule and files containing json file", () => {
296315
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withFiles.json");
297316
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"compilerOptions": {
3+
"composite": true,
4+
"target": "esnext",
5+
"moduleResolution": "node",
6+
"module": "commonjs",
7+
"resolveJsonModule": true,
8+
"esModuleInterop": true,
9+
"allowSyntheticDefaultImports": true,
10+
"outDir": "dist",
11+
"skipDefaultLibCheck": true
12+
},
13+
"include": [
14+
"src/**/*", "src/**/*.json"
15+
]
16+
}

0 commit comments

Comments
 (0)