Skip to content

Commit a4a1bed

Browse files
authored
Add showConfig tsc flag for debugging configs (#27353)
* Add showConfig tsc flag for debugging configs * Merge showConfig implementation with init implementation, add basic unit tests * Fix lint * Add missing semicolon * showConfig when theres no config file
1 parent 3a2f7c0 commit a4a1bed

File tree

15 files changed

+233
-50
lines changed

15 files changed

+233
-50
lines changed

src/compiler/commandLineParser.ts

Lines changed: 131 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ namespace ts {
164164
category: Diagnostics.Command_line_Options,
165165
description: Diagnostics.Stylize_errors_and_messages_using_color_and_context_experimental
166166
},
167+
{
168+
name: "showConfig",
169+
type: "boolean",
170+
category: Diagnostics.Command_line_Options,
171+
isCommandLineOnly: true,
172+
description: Diagnostics.Print_the_final_configuration_instead_of_building
173+
},
167174

168175
// Basic
169176
{
@@ -1654,72 +1661,146 @@ namespace ts {
16541661
}
16551662

16561663
/**
1657-
* Generate tsconfig configuration when running command line "--init"
1658-
* @param options commandlineOptions to be generated into tsconfig.json
1659-
* @param fileNames array of filenames to be generated into tsconfig.json
1664+
* Generate an uncommented, complete tsconfig for use with "--showConfig"
1665+
* @param configParseResult options to be generated into tsconfig.json
1666+
* @param configFileName name of the parsed config file - output paths will be generated relative to this
1667+
* @param host provides current directory and case sensitivity services
16601668
*/
1661-
/* @internal */
1662-
export function generateTSConfig(options: CompilerOptions, fileNames: ReadonlyArray<string>, newLine: string): string {
1663-
const compilerOptions = extend(options, defaultInitCompilerOptions);
1664-
const compilerOptionsMap = serializeCompilerOptions(compilerOptions);
1665-
return writeConfigurations();
1669+
/** @internal */
1670+
export function convertToTSConfig(configParseResult: ParsedCommandLine, configFileName: string, host: { getCurrentDirectory(): string, useCaseSensitiveFileNames: boolean }): object {
1671+
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
1672+
const files = map(
1673+
filter(
1674+
configParseResult.fileNames,
1675+
!configParseResult.configFileSpecs ? _ => false : matchesSpecs(
1676+
configFileName,
1677+
configParseResult.configFileSpecs.validatedIncludeSpecs,
1678+
configParseResult.configFileSpecs.validatedExcludeSpecs
1679+
)
1680+
),
1681+
f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), f, getCanonicalFileName)
1682+
);
1683+
const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames });
1684+
const config = {
1685+
compilerOptions: {
1686+
...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}),
1687+
showConfig: undefined,
1688+
configFile: undefined,
1689+
configFilePath: undefined,
1690+
help: undefined,
1691+
init: undefined,
1692+
listFiles: undefined,
1693+
listEmittedFiles: undefined,
1694+
project: undefined,
1695+
},
1696+
references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath, originalPath: undefined })),
1697+
files: length(files) ? files : undefined,
1698+
...(configParseResult.configFileSpecs ? {
1699+
include: filterSameAsDefaultInclude(configParseResult.configFileSpecs.validatedIncludeSpecs),
1700+
exclude: configParseResult.configFileSpecs.validatedExcludeSpecs
1701+
} : {}),
1702+
compilerOnSave: !!configParseResult.compileOnSave ? true : undefined
1703+
};
1704+
return config;
1705+
}
16661706

1667-
function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map<string | number> | undefined {
1668-
if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean") {
1669-
// this is of a type CommandLineOptionOfPrimitiveType
1670-
return undefined;
1671-
}
1672-
else if (optionDefinition.type === "list") {
1673-
return getCustomTypeMapOfCommandLineOption(optionDefinition.element);
1674-
}
1675-
else {
1676-
return (<CommandLineOptionOfCustomType>optionDefinition).type;
1707+
function filterSameAsDefaultInclude(specs: ReadonlyArray<string> | undefined) {
1708+
if (!length(specs)) return undefined;
1709+
if (length(specs) !== 1) return specs;
1710+
if (specs![0] === "**/*") return undefined;
1711+
return specs;
1712+
}
1713+
1714+
function matchesSpecs(path: string, includeSpecs: ReadonlyArray<string> | undefined, excludeSpecs: ReadonlyArray<string> | undefined): (path: string) => boolean {
1715+
if (!includeSpecs) return _ => false;
1716+
const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, sys.useCaseSensitiveFileNames, sys.getCurrentDirectory());
1717+
const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, sys.useCaseSensitiveFileNames);
1718+
const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, sys.useCaseSensitiveFileNames);
1719+
if (includeRe) {
1720+
if (excludeRe) {
1721+
return path => includeRe.test(path) && !excludeRe.test(path);
16771722
}
1723+
return path => includeRe.test(path);
1724+
}
1725+
if (excludeRe) {
1726+
return path => !excludeRe.test(path);
16781727
}
1728+
return _ => false;
1729+
}
16791730

1680-
function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: Map<string | number>): string | undefined {
1681-
// There is a typeMap associated with this command-line option so use it to map value back to its name
1682-
return forEachEntry(customTypeMap, (mapValue, key) => {
1683-
if (mapValue === value) {
1684-
return key;
1685-
}
1686-
});
1731+
function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map<string | number> | undefined {
1732+
if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean") {
1733+
// this is of a type CommandLineOptionOfPrimitiveType
1734+
return undefined;
1735+
}
1736+
else if (optionDefinition.type === "list") {
1737+
return getCustomTypeMapOfCommandLineOption(optionDefinition.element);
1738+
}
1739+
else {
1740+
return (<CommandLineOptionOfCustomType>optionDefinition).type;
16871741
}
1742+
}
16881743

1689-
function serializeCompilerOptions(options: CompilerOptions): Map<CompilerOptionsValue> {
1690-
const result = createMap<CompilerOptionsValue>();
1691-
const optionsNameMap = getOptionNameMap().optionNameMap;
1744+
function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: Map<string | number>): string | undefined {
1745+
// There is a typeMap associated with this command-line option so use it to map value back to its name
1746+
return forEachEntry(customTypeMap, (mapValue, key) => {
1747+
if (mapValue === value) {
1748+
return key;
1749+
}
1750+
});
1751+
}
16921752

1693-
for (const name in options) {
1694-
if (hasProperty(options, name)) {
1695-
// tsconfig only options cannot be specified via command line,
1696-
// so we can assume that only types that can appear here string | number | boolean
1697-
if (optionsNameMap.has(name) && optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options) {
1698-
continue;
1699-
}
1700-
const value = <CompilerOptionsValue>options[name];
1701-
const optionDefinition = optionsNameMap.get(name.toLowerCase());
1702-
if (optionDefinition) {
1703-
const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition);
1704-
if (!customTypeMap) {
1705-
// There is no map associated with this compiler option then use the value as-is
1706-
// This is the case if the value is expect to be string, number, boolean or list of string
1753+
function serializeCompilerOptions(options: CompilerOptions, pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean }): Map<CompilerOptionsValue> {
1754+
const result = createMap<CompilerOptionsValue>();
1755+
const optionsNameMap = getOptionNameMap().optionNameMap;
1756+
const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames);
1757+
1758+
for (const name in options) {
1759+
if (hasProperty(options, name)) {
1760+
// tsconfig only options cannot be specified via command line,
1761+
// so we can assume that only types that can appear here string | number | boolean
1762+
if (optionsNameMap.has(name) && optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options) {
1763+
continue;
1764+
}
1765+
const value = <CompilerOptionsValue>options[name];
1766+
const optionDefinition = optionsNameMap.get(name.toLowerCase());
1767+
if (optionDefinition) {
1768+
const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition);
1769+
if (!customTypeMap) {
1770+
// There is no map associated with this compiler option then use the value as-is
1771+
// This is the case if the value is expect to be string, number, boolean or list of string
1772+
if (pathOptions && optionDefinition.isFilePath) {
1773+
result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(value as string, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!));
1774+
}
1775+
else {
17071776
result.set(name, value);
17081777
}
1778+
}
1779+
else {
1780+
if (optionDefinition.type === "list") {
1781+
result.set(name, (value as ReadonlyArray<string | number>).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217
1782+
}
17091783
else {
1710-
if (optionDefinition.type === "list") {
1711-
result.set(name, (value as ReadonlyArray<string | number>).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217
1712-
}
1713-
else {
1714-
// There is a typeMap associated with this command-line option so use it to map value back to its name
1715-
result.set(name, getNameOfCompilerOptionValue(value, customTypeMap));
1716-
}
1784+
// There is a typeMap associated with this command-line option so use it to map value back to its name
1785+
result.set(name, getNameOfCompilerOptionValue(value, customTypeMap));
17171786
}
17181787
}
17191788
}
17201789
}
1721-
return result;
17221790
}
1791+
return result;
1792+
}
1793+
1794+
/**
1795+
* Generate tsconfig configuration when running command line "--init"
1796+
* @param options commandlineOptions to be generated into tsconfig.json
1797+
* @param fileNames array of filenames to be generated into tsconfig.json
1798+
*/
1799+
/* @internal */
1800+
export function generateTSConfig(options: CompilerOptions, fileNames: ReadonlyArray<string>, newLine: string): string {
1801+
const compilerOptions = extend(options, defaultInitCompilerOptions);
1802+
const compilerOptionsMap = serializeCompilerOptions(compilerOptions);
1803+
return writeConfigurations();
17231804

17241805
function getDefaultValueForOption(option: CommandLineOption) {
17251806
switch (option.type) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,10 @@
10071007
"category": "Error",
10081008
"code": 1349
10091009
},
1010+
"Print the final configuration instead of building.": {
1011+
"category": "Message",
1012+
"code": 1350
1013+
},
10101014

10111015
"Duplicate identifier '{0}'.": {
10121016
"category": "Error",

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4556,6 +4556,7 @@ namespace ts {
45564556
/*@internal*/ version?: boolean;
45574557
/*@internal*/ watch?: boolean;
45584558
esModuleInterop?: boolean;
4559+
/* @internal */ showConfig?: boolean;
45594560

45604561
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
45614562
}

src/testRunner/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"unittests/reuseProgramStructure.ts",
7676
"unittests/session.ts",
7777
"unittests/semver.ts",
78+
"unittests/showConfig.ts",
7879
"unittests/symbolWalker.ts",
7980
"unittests/telemetry.ts",
8081
"unittests/textChanges.ts",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace ts {
2+
describe("showTSConfig", () => {
3+
function showTSConfigCorrectly(name: string, commandLinesArgs: string[]) {
4+
describe(name, () => {
5+
const commandLine = parseCommandLine(commandLinesArgs);
6+
const initResult = convertToTSConfig(commandLine, `/${name}/tsconfig.json`, { getCurrentDirectory() { return `/${name}`; }, useCaseSensitiveFileNames: true });
7+
const outputFileName = `showConfig/${name.replace(/[^a-z0-9\-. ]/ig, "")}/tsconfig.json`;
8+
9+
it(`Correct output for ${outputFileName}`, () => {
10+
// tslint:disable-next-line:no-null-keyword
11+
Harness.Baseline.runBaseline(outputFileName, JSON.stringify(initResult, null, 4) + "\n");
12+
});
13+
});
14+
}
15+
16+
showTSConfigCorrectly("Default initialized TSConfig", ["--showConfig"]);
17+
18+
showTSConfigCorrectly("Show TSConfig with files options", ["--showConfig", "file0.st", "file1.ts", "file2.ts"]);
19+
20+
showTSConfigCorrectly("Show TSConfig with boolean value compiler options", ["--showConfig", "--noUnusedLocals"]);
21+
22+
showTSConfigCorrectly("Show TSConfig with enum value compiler options", ["--showConfig", "--target", "es5", "--jsx", "react"]);
23+
24+
showTSConfigCorrectly("Show TSConfig with list compiler options", ["--showConfig", "--types", "jquery,mocha"]);
25+
26+
showTSConfigCorrectly("Show TSConfig with list compiler options with enum value", ["--showConfig", "--lib", "es5,es2015.core"]);
27+
28+
showTSConfigCorrectly("Show TSConfig with incorrect compiler option", ["--showConfig", "--someNonExistOption"]);
29+
30+
showTSConfigCorrectly("Show TSConfig with incorrect compiler option value", ["--showConfig", "--lib", "nonExistLib,es5,es2015.promise"]);
31+
32+
showTSConfigCorrectly("Show TSConfig with advanced options", ["--showConfig", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]);
33+
});
34+
}

src/tsc/tsc.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ namespace ts {
132132
const commandLineOptions = commandLine.options;
133133
if (configFileName) {
134134
const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions, sys, reportDiagnostic)!; // TODO: GH#18217
135+
if (commandLineOptions.showConfig) {
136+
// tslint:disable-next-line:no-null-keyword
137+
sys.write(JSON.stringify(convertToTSConfig(configParseResult, configFileName, sys), null, 4) + sys.newLine);
138+
return sys.exit(ExitStatus.Success);
139+
}
135140
updateReportDiagnostic(configParseResult.options);
136141
if (isWatchSet(configParseResult.options)) {
137142
reportWatchModeWithoutSysSupport();
@@ -142,6 +147,11 @@ namespace ts {
142147
}
143148
}
144149
else {
150+
if (commandLineOptions.showConfig) {
151+
// tslint:disable-next-line:no-null-keyword
152+
sys.write(JSON.stringify(convertToTSConfig(commandLine, combinePaths(sys.getCurrentDirectory(), "tsconfig.json"), sys), null, 4) + sys.newLine);
153+
return sys.exit(ExitStatus.Success);
154+
}
145155
updateReportDiagnostic(commandLineOptions);
146156
if (isWatchSet(commandLineOptions)) {
147157
reportWatchModeWithoutSysSupport();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"compilerOptions": {}
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"declaration": true,
4+
"declarationDir": "./lib",
5+
"skipLibCheck": true,
6+
"noErrorTruncation": true
7+
}
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compilerOptions": {
3+
"noUnusedLocals": true
4+
}
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"jsx": "react"
5+
}
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"compilerOptions": {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es5",
5+
"es2015.promise"
6+
]
7+
}
8+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"compilerOptions": {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es5",
5+
"es2015.core"
6+
]
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"types": [
4+
"jquery",
5+
"mocha"
6+
]
7+
}
8+
}

0 commit comments

Comments
 (0)