Add more errors; commandline help for --build; invalid flag combo detection

This commit is contained in:
Ryan Cavanaugh 2018-05-30 10:00:35 -07:00
parent 129f747ccc
commit f8c4301f14
5 changed files with 236 additions and 123 deletions

View File

@ -49,6 +49,14 @@ namespace ts {
paramType: Diagnostics.FILE_OR_DIRECTORY,
description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json,
},
{
name: "build",
type: "boolean",
shortName: "b",
showInSimplifiedHelpView: true,
category: Diagnostics.Command_line_Options,
description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date
},
{
name: "pretty",
type: "boolean",
@ -943,6 +951,125 @@ namespace ts {
}
function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string {
const diagnostic = createCompilerDiagnostic.apply(undefined, arguments);
return <string>diagnostic.messageText;
}
/* @internal */
export function printVersion() {
sys.write(getDiagnosticText(Diagnostics.Version_0, version) + sys.newLine);
}
/* @internal */
export function printHelp(optionsList: CommandLineOption[], syntaxPrefix = "") {
const output: string[] = [];
// We want to align our "syntax" and "examples" commands to a certain margin.
const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length;
const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length;
let marginLength = Math.max(syntaxLength, examplesLength);
// Build up the syntactic skeleton.
let syntax = makePadding(marginLength - syntaxLength);
syntax += `tsc ${syntaxPrefix}[${getDiagnosticText(Diagnostics.options)}] [${getDiagnosticText(Diagnostics.file)}...]`;
output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax));
output.push(sys.newLine + sys.newLine);
// Build up the list of examples.
const padding = makePadding(marginLength);
output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine);
output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine);
output.push(padding + "tsc @args.txt" + sys.newLine);
output.push(padding + "tsc --build tsconfig.json" + sys.newLine);
output.push(sys.newLine);
output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine);
// We want our descriptions to align at the same column in our output,
// so we keep track of the longest option usage string.
marginLength = 0;
const usageColumn: string[] = []; // Things like "-d, --declaration" go in here.
const descriptionColumn: string[] = [];
const optionsDescriptionMap = createMap<string[]>(); // Map between option.description and list of option.type if it is a kind
for (const option of optionsList) {
// If an option lacks a description,
// it is not officially supported.
if (!option.description) {
continue;
}
let usageText = " ";
if (option.shortName) {
usageText += "-" + option.shortName;
usageText += getParamType(option);
usageText += ", ";
}
usageText += "--" + option.name;
usageText += getParamType(option);
usageColumn.push(usageText);
let description: string;
if (option.name === "lib") {
description = getDiagnosticText(option.description);
const element = (<CommandLineOptionOfListType>option).element;
const typeMap = <Map<number | string>>element.type;
optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`));
}
else {
description = getDiagnosticText(option.description);
}
descriptionColumn.push(description);
// Set the new margin for the description column if necessary.
marginLength = Math.max(usageText.length, marginLength);
}
// Special case that can't fit in the loop.
const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">";
usageColumn.push(usageText);
descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file));
marginLength = Math.max(usageText.length, marginLength);
// Print out each row, aligning all the descriptions on the same column.
for (let i = 0; i < usageColumn.length; i++) {
const usage = usageColumn[i];
const description = descriptionColumn[i];
const kindsList = optionsDescriptionMap.get(description);
output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine);
if (kindsList) {
output.push(makePadding(marginLength + 4));
for (const kind of kindsList) {
output.push(kind + " ");
}
output.push(sys.newLine);
}
}
for (const line of output) {
sys.write(line);
}
return;
function getParamType(option: CommandLineOption) {
if (option.paramType !== undefined) {
return " " + getDiagnosticText(option.paramType);
}
return "";
}
function makePadding(paddingLength: number): string {
return Array(paddingLength + 1).join(" ");
}
}
export type DiagnosticReporter = (diagnostic: Diagnostic) => void;
/**
* Reports config file diagnostics

View File

@ -3657,6 +3657,34 @@
"category": "Message",
"code": 6362
},
"Build one or more projects and their dependencies, if out-of-date": {
"category": "Message",
"code": 6363
},
"Delete the outputs of all projects": {
"category": "Message",
"code": 6364
},
"Enable verbose logging": {
"category": "Message",
"code": 6365
},
"Show what would be built (or deleted, if specified with --clean)": {
"category": "Message",
"code": 6366
},
"Build all projects, including those that appear to be up-to-date": {
"category": "Message",
"code": 6367
},
"Option '--build' must be the first command line argument.": {
"category": "Error",
"code": 6368
},
"Options '{0}' and '{1}' cannot be combined.": {
"category": "Error",
"code": 6369
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",

View File

@ -338,11 +338,48 @@ namespace ts {
};
}
const buildOpts: CommandLineOption[] = [
{
name: "verbose",
shortName: "v",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Enable_verbose_logging,
type: "boolean"
},
{
name: "dry",
shortName: "d",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean,
type: "boolean"
},
{
name: "force",
shortName: "f",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date,
type: "boolean"
},
{
name: "clean",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Delete_the_outputs_of_all_projects,
type: "boolean"
},
{
name: "watch",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Watch_input_files,
type: "boolean"
}
];
export function performBuild(host: CompilerHost, reportDiagnostic: DiagnosticReporter, args: string[]) {
let verbose = false;
let dry = false;
let force = false;
let clean = false;
let watch = false;
const projects: string[] = [];
for (const arg of args) {
@ -362,11 +399,38 @@ namespace ts {
case "--clean":
clean = true;
continue;
case "--watch":
case "-w":
watch = true;
continue;
case "--?":
case "-?":
case "--help":
return printHelp(buildOpts, "--build ");
}
// Not a flag, parse as filename
addProject(arg);
}
// Nonsensical combinations
if (clean && force) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force"));
return;
}
if (clean && verbose) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose"));
return;
}
if (clean && watch) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch"));
return;
}
if (watch && dry) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"));
return;
}
if (projects.length === 0) {
// tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ."
addProject(".");

View File

@ -12,11 +12,6 @@ namespace ts {
return count;
}
function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string {
const diagnostic = createCompilerDiagnostic.apply(undefined, arguments);
return <string>diagnostic.messageText;
}
let reportDiagnostic = createDiagnosticReporter(sys);
function updateReportDiagnostic(options: CompilerOptions) {
if (shouldBePretty(options)) {
@ -46,6 +41,13 @@ namespace ts {
return s;
}
function getOptionsForHelp(commandLine: ParsedCommandLine) {
// Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch")
return !!commandLine.options.all ?
sort(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) :
filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView);
}
export function executeCommandLine(args: string[]): void {
if ((args[0].toLowerCase() === "--build") || (args[0].toLowerCase() === "-b")) {
return performBuild(createCompilerHost({}), createDiagnosticReporter(sys), args.slice(1));
@ -53,6 +55,11 @@ namespace ts {
const commandLine = parseCommandLine(args);
if (commandLine.options.build) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_build_must_be_the_first_command_line_argument));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
// Configuration file name (if any)
let configFileName: string | undefined;
if (commandLine.options.locale) {
@ -78,7 +85,7 @@ namespace ts {
if (commandLine.options.help || commandLine.options.all) {
printVersion();
printHelp(!!commandLine.options.all);
printHelp(getOptionsForHelp(commandLine));
return sys.exit(ExitStatus.Success);
}
@ -111,7 +118,7 @@ namespace ts {
if (commandLine.fileNames.length === 0 && !configFileName) {
printVersion();
printHelp(!!commandLine.options.all);
printHelp(getOptionsForHelp(commandLine));
return sys.exit(ExitStatus.Success);
}
@ -275,122 +282,6 @@ namespace ts {
}
}
function printVersion() {
sys.write(getDiagnosticText(Diagnostics.Version_0, version) + sys.newLine);
}
function printHelp(showAllOptions: boolean) {
const output: string[] = [];
// We want to align our "syntax" and "examples" commands to a certain margin.
const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length;
const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length;
let marginLength = Math.max(syntaxLength, examplesLength);
// Build up the syntactic skeleton.
let syntax = makePadding(marginLength - syntaxLength);
syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]";
output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax));
output.push(sys.newLine + sys.newLine);
// Build up the list of examples.
const padding = makePadding(marginLength);
output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine);
output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine);
output.push(padding + "tsc @args.txt" + sys.newLine);
output.push(sys.newLine);
output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine);
// Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch")
const optsList = showAllOptions ?
sort(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) :
filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView);
// We want our descriptions to align at the same column in our output,
// so we keep track of the longest option usage string.
marginLength = 0;
const usageColumn: string[] = []; // Things like "-d, --declaration" go in here.
const descriptionColumn: string[] = [];
const optionsDescriptionMap = createMap<string[]>(); // Map between option.description and list of option.type if it is a kind
for (const option of optsList) {
// If an option lacks a description,
// it is not officially supported.
if (!option.description) {
continue;
}
let usageText = " ";
if (option.shortName) {
usageText += "-" + option.shortName;
usageText += getParamType(option);
usageText += ", ";
}
usageText += "--" + option.name;
usageText += getParamType(option);
usageColumn.push(usageText);
let description: string;
if (option.name === "lib") {
description = getDiagnosticText(option.description);
const element = (<CommandLineOptionOfListType>option).element;
const typeMap = <Map<number | string>>element.type;
optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`));
}
else {
description = getDiagnosticText(option.description);
}
descriptionColumn.push(description);
// Set the new margin for the description column if necessary.
marginLength = Math.max(usageText.length, marginLength);
}
// Special case that can't fit in the loop.
const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">";
usageColumn.push(usageText);
descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file));
marginLength = Math.max(usageText.length, marginLength);
// Print out each row, aligning all the descriptions on the same column.
for (let i = 0; i < usageColumn.length; i++) {
const usage = usageColumn[i];
const description = descriptionColumn[i];
const kindsList = optionsDescriptionMap.get(description);
output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine);
if (kindsList) {
output.push(makePadding(marginLength + 4));
for (const kind of kindsList) {
output.push(kind + " ");
}
output.push(sys.newLine);
}
}
for (const line of output) {
sys.write(line);
}
return;
function getParamType(option: CommandLineOption) {
if (option.paramType !== undefined) {
return " " + getDiagnosticText(option.paramType);
}
return "";
}
function makePadding(paddingLength: number): string {
return Array(paddingLength + 1).join(" ");
}
}
function writeConfigFile(options: CompilerOptions, fileNames: string[]) {
const currentDirectory = sys.getCurrentDirectory();
const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json"));

View File

@ -4281,6 +4281,9 @@ namespace ts {
allowUnusedLabels?: boolean;
alwaysStrict?: boolean; // Always combine with strict property
baseUrl?: string;
/** An error if set - this should only go through the -b pipeline and not actually be observed */
/*@internal*/
build?: boolean;
charset?: string;
checkJs?: boolean;
/* @internal */ configFilePath?: string;