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
This commit is contained in:
Wesley Wigham
2018-10-31 15:57:09 -07:00
committed by GitHub
parent 3a2f7c0df1
commit a4a1bed88b
15 changed files with 240 additions and 57 deletions

View File

@@ -164,6 +164,13 @@ namespace ts {
category: Diagnostics.Command_line_Options,
description: Diagnostics.Stylize_errors_and_messages_using_color_and_context_experimental
},
{
name: "showConfig",
type: "boolean",
category: Diagnostics.Command_line_Options,
isCommandLineOnly: true,
description: Diagnostics.Print_the_final_configuration_instead_of_building
},
// Basic
{
@@ -1653,6 +1660,137 @@ namespace ts {
return false;
}
/**
* Generate an uncommented, complete tsconfig for use with "--showConfig"
* @param configParseResult options to be generated into tsconfig.json
* @param configFileName name of the parsed config file - output paths will be generated relative to this
* @param host provides current directory and case sensitivity services
*/
/** @internal */
export function convertToTSConfig(configParseResult: ParsedCommandLine, configFileName: string, host: { getCurrentDirectory(): string, useCaseSensitiveFileNames: boolean }): object {
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
const files = map(
filter(
configParseResult.fileNames,
!configParseResult.configFileSpecs ? _ => false : matchesSpecs(
configFileName,
configParseResult.configFileSpecs.validatedIncludeSpecs,
configParseResult.configFileSpecs.validatedExcludeSpecs
)
),
f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), f, getCanonicalFileName)
);
const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames });
const config = {
compilerOptions: {
...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}),
showConfig: undefined,
configFile: undefined,
configFilePath: undefined,
help: undefined,
init: undefined,
listFiles: undefined,
listEmittedFiles: undefined,
project: undefined,
},
references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath, originalPath: undefined })),
files: length(files) ? files : undefined,
...(configParseResult.configFileSpecs ? {
include: filterSameAsDefaultInclude(configParseResult.configFileSpecs.validatedIncludeSpecs),
exclude: configParseResult.configFileSpecs.validatedExcludeSpecs
} : {}),
compilerOnSave: !!configParseResult.compileOnSave ? true : undefined
};
return config;
}
function filterSameAsDefaultInclude(specs: ReadonlyArray<string> | undefined) {
if (!length(specs)) return undefined;
if (length(specs) !== 1) return specs;
if (specs![0] === "**/*") return undefined;
return specs;
}
function matchesSpecs(path: string, includeSpecs: ReadonlyArray<string> | undefined, excludeSpecs: ReadonlyArray<string> | undefined): (path: string) => boolean {
if (!includeSpecs) return _ => false;
const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, sys.useCaseSensitiveFileNames, sys.getCurrentDirectory());
const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, sys.useCaseSensitiveFileNames);
const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, sys.useCaseSensitiveFileNames);
if (includeRe) {
if (excludeRe) {
return path => includeRe.test(path) && !excludeRe.test(path);
}
return path => includeRe.test(path);
}
if (excludeRe) {
return path => !excludeRe.test(path);
}
return _ => false;
}
function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map<string | number> | undefined {
if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean") {
// this is of a type CommandLineOptionOfPrimitiveType
return undefined;
}
else if (optionDefinition.type === "list") {
return getCustomTypeMapOfCommandLineOption(optionDefinition.element);
}
else {
return (<CommandLineOptionOfCustomType>optionDefinition).type;
}
}
function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: Map<string | number>): string | undefined {
// There is a typeMap associated with this command-line option so use it to map value back to its name
return forEachEntry(customTypeMap, (mapValue, key) => {
if (mapValue === value) {
return key;
}
});
}
function serializeCompilerOptions(options: CompilerOptions, pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean }): Map<CompilerOptionsValue> {
const result = createMap<CompilerOptionsValue>();
const optionsNameMap = getOptionNameMap().optionNameMap;
const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames);
for (const name in options) {
if (hasProperty(options, name)) {
// tsconfig only options cannot be specified via command line,
// so we can assume that only types that can appear here string | number | boolean
if (optionsNameMap.has(name) && optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options) {
continue;
}
const value = <CompilerOptionsValue>options[name];
const optionDefinition = optionsNameMap.get(name.toLowerCase());
if (optionDefinition) {
const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition);
if (!customTypeMap) {
// There is no map associated with this compiler option then use the value as-is
// This is the case if the value is expect to be string, number, boolean or list of string
if (pathOptions && optionDefinition.isFilePath) {
result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(value as string, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!));
}
else {
result.set(name, value);
}
}
else {
if (optionDefinition.type === "list") {
result.set(name, (value as ReadonlyArray<string | number>).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217
}
else {
// There is a typeMap associated with this command-line option so use it to map value back to its name
result.set(name, getNameOfCompilerOptionValue(value, customTypeMap));
}
}
}
}
}
return result;
}
/**
* Generate tsconfig configuration when running command line "--init"
* @param options commandlineOptions to be generated into tsconfig.json
@@ -1664,63 +1802,6 @@ namespace ts {
const compilerOptionsMap = serializeCompilerOptions(compilerOptions);
return writeConfigurations();
function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map<string | number> | undefined {
if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean") {
// this is of a type CommandLineOptionOfPrimitiveType
return undefined;
}
else if (optionDefinition.type === "list") {
return getCustomTypeMapOfCommandLineOption(optionDefinition.element);
}
else {
return (<CommandLineOptionOfCustomType>optionDefinition).type;
}
}
function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: Map<string | number>): string | undefined {
// There is a typeMap associated with this command-line option so use it to map value back to its name
return forEachEntry(customTypeMap, (mapValue, key) => {
if (mapValue === value) {
return key;
}
});
}
function serializeCompilerOptions(options: CompilerOptions): Map<CompilerOptionsValue> {
const result = createMap<CompilerOptionsValue>();
const optionsNameMap = getOptionNameMap().optionNameMap;
for (const name in options) {
if (hasProperty(options, name)) {
// tsconfig only options cannot be specified via command line,
// so we can assume that only types that can appear here string | number | boolean
if (optionsNameMap.has(name) && optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options) {
continue;
}
const value = <CompilerOptionsValue>options[name];
const optionDefinition = optionsNameMap.get(name.toLowerCase());
if (optionDefinition) {
const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition);
if (!customTypeMap) {
// There is no map associated with this compiler option then use the value as-is
// This is the case if the value is expect to be string, number, boolean or list of string
result.set(name, value);
}
else {
if (optionDefinition.type === "list") {
result.set(name, (value as ReadonlyArray<string | number>).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217
}
else {
// There is a typeMap associated with this command-line option so use it to map value back to its name
result.set(name, getNameOfCompilerOptionValue(value, customTypeMap));
}
}
}
}
}
return result;
}
function getDefaultValueForOption(option: CommandLineOption) {
switch (option.type) {
case "number":

View File

@@ -1007,6 +1007,10 @@
"category": "Error",
"code": 1349
},
"Print the final configuration instead of building.": {
"category": "Message",
"code": 1350
},
"Duplicate identifier '{0}'.": {
"category": "Error",

View File

@@ -4556,6 +4556,7 @@ namespace ts {
/*@internal*/ version?: boolean;
/*@internal*/ watch?: boolean;
esModuleInterop?: boolean;
/* @internal */ showConfig?: boolean;
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}

View File

@@ -75,6 +75,7 @@
"unittests/reuseProgramStructure.ts",
"unittests/session.ts",
"unittests/semver.ts",
"unittests/showConfig.ts",
"unittests/symbolWalker.ts",
"unittests/telemetry.ts",
"unittests/textChanges.ts",

View File

@@ -0,0 +1,34 @@
namespace ts {
describe("showTSConfig", () => {
function showTSConfigCorrectly(name: string, commandLinesArgs: string[]) {
describe(name, () => {
const commandLine = parseCommandLine(commandLinesArgs);
const initResult = convertToTSConfig(commandLine, `/${name}/tsconfig.json`, { getCurrentDirectory() { return `/${name}`; }, useCaseSensitiveFileNames: true });
const outputFileName = `showConfig/${name.replace(/[^a-z0-9\-. ]/ig, "")}/tsconfig.json`;
it(`Correct output for ${outputFileName}`, () => {
// tslint:disable-next-line:no-null-keyword
Harness.Baseline.runBaseline(outputFileName, JSON.stringify(initResult, null, 4) + "\n");
});
});
}
showTSConfigCorrectly("Default initialized TSConfig", ["--showConfig"]);
showTSConfigCorrectly("Show TSConfig with files options", ["--showConfig", "file0.st", "file1.ts", "file2.ts"]);
showTSConfigCorrectly("Show TSConfig with boolean value compiler options", ["--showConfig", "--noUnusedLocals"]);
showTSConfigCorrectly("Show TSConfig with enum value compiler options", ["--showConfig", "--target", "es5", "--jsx", "react"]);
showTSConfigCorrectly("Show TSConfig with list compiler options", ["--showConfig", "--types", "jquery,mocha"]);
showTSConfigCorrectly("Show TSConfig with list compiler options with enum value", ["--showConfig", "--lib", "es5,es2015.core"]);
showTSConfigCorrectly("Show TSConfig with incorrect compiler option", ["--showConfig", "--someNonExistOption"]);
showTSConfigCorrectly("Show TSConfig with incorrect compiler option value", ["--showConfig", "--lib", "nonExistLib,es5,es2015.promise"]);
showTSConfigCorrectly("Show TSConfig with advanced options", ["--showConfig", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]);
});
}

View File

@@ -132,6 +132,11 @@ namespace ts {
const commandLineOptions = commandLine.options;
if (configFileName) {
const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions, sys, reportDiagnostic)!; // TODO: GH#18217
if (commandLineOptions.showConfig) {
// tslint:disable-next-line:no-null-keyword
sys.write(JSON.stringify(convertToTSConfig(configParseResult, configFileName, sys), null, 4) + sys.newLine);
return sys.exit(ExitStatus.Success);
}
updateReportDiagnostic(configParseResult.options);
if (isWatchSet(configParseResult.options)) {
reportWatchModeWithoutSysSupport();
@@ -142,6 +147,11 @@ namespace ts {
}
}
else {
if (commandLineOptions.showConfig) {
// tslint:disable-next-line:no-null-keyword
sys.write(JSON.stringify(convertToTSConfig(commandLine, combinePaths(sys.getCurrentDirectory(), "tsconfig.json"), sys), null, 4) + sys.newLine);
return sys.exit(ExitStatus.Success);
}
updateReportDiagnostic(commandLineOptions);
if (isWatchSet(commandLineOptions)) {
reportWatchModeWithoutSysSupport();

View File

@@ -0,0 +1,3 @@
{
"compilerOptions": {}
}

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./lib",
"skipLibCheck": true,
"noErrorTruncation": true
}
}

View File

@@ -0,0 +1,5 @@
{
"compilerOptions": {
"noUnusedLocals": true
}
}

View File

@@ -0,0 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"jsx": "react"
}
}

View File

@@ -0,0 +1,3 @@
{
"compilerOptions": {}
}

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"lib": [
"es5",
"es2015.promise"
]
}
}

View File

@@ -0,0 +1,3 @@
{
"compilerOptions": {}
}

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"lib": [
"es5",
"es2015.core"
]
}
}

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"types": [
"jquery",
"mocha"
]
}
}