Skip to content

Commit e547cdf

Browse files
authored
Merge pull request #27078 from Microsoft/commonCompilerOptionsWithBuild
Parse selected command line options with build and use them as base for solution building
2 parents 47f311a + ebb89ba commit e547cdf

File tree

7 files changed

+201
-113
lines changed

7 files changed

+201
-113
lines changed

src/compiler/commandLineParser.ts

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ namespace ts {
6262
/* @internal */
6363
export const libMap = createMapFromEntries(libEntries);
6464

65-
const commonOptionsWithBuild: CommandLineOption[] = [
65+
/* @internal */
66+
export const commonOptionsWithBuild: CommandLineOption[] = [
6667
{
6768
name: "help",
6869
shortName: "h",
@@ -83,6 +84,18 @@ namespace ts {
8384
category: Diagnostics.Command_line_Options,
8485
description: Diagnostics.Whether_to_keep_outdated_console_output_in_watch_mode_instead_of_clearing_the_screen,
8586
},
87+
{
88+
name: "listFiles",
89+
type: "boolean",
90+
category: Diagnostics.Advanced_Options,
91+
description: Diagnostics.Print_names_of_files_part_of_the_compilation
92+
},
93+
{
94+
name: "listEmittedFiles",
95+
type: "boolean",
96+
category: Diagnostics.Advanced_Options,
97+
description: Diagnostics.Print_names_of_generated_files_part_of_the_compilation
98+
},
8699
{
87100
name: "watch",
88101
shortName: "w",
@@ -561,18 +574,6 @@ namespace ts {
561574
category: Diagnostics.Advanced_Options,
562575
description: Diagnostics.Include_modules_imported_with_json_extension
563576
},
564-
{
565-
name: "listFiles",
566-
type: "boolean",
567-
category: Diagnostics.Advanced_Options,
568-
description: Diagnostics.Print_names_of_files_part_of_the_compilation
569-
},
570-
{
571-
name: "listEmittedFiles",
572-
type: "boolean",
573-
category: Diagnostics.Advanced_Options,
574-
description: Diagnostics.Print_names_of_generated_files_part_of_the_compilation
575-
},
576577

577578
{
578579
name: "out",
@@ -903,17 +904,27 @@ namespace ts {
903904
}
904905
}
905906

906-
export function parseCommandLine(commandLine: ReadonlyArray<string>, readFile?: (path: string) => string | undefined): ParsedCommandLine {
907-
const options: CompilerOptions = {};
907+
/* @internal */
908+
export interface OptionsBase {
909+
[option: string]: CompilerOptionsValue | undefined;
910+
}
911+
912+
/** Tuple with error messages for 'unknown compiler option', 'option requires type' */
913+
type ParseCommandLineWorkerDiagnostics = [DiagnosticMessage, DiagnosticMessage];
914+
915+
function parseCommandLineWorker(
916+
getOptionNameMap: () => OptionNameMap,
917+
[unknownOptionDiagnostic, optionTypeMismatchDiagnostic]: ParseCommandLineWorkerDiagnostics,
918+
commandLine: ReadonlyArray<string>,
919+
readFile?: (path: string) => string | undefined) {
920+
const options = {} as OptionsBase;
908921
const fileNames: string[] = [];
909-
const projectReferences: ProjectReference[] | undefined = undefined;
910922
const errors: Diagnostic[] = [];
911923

912924
parseStrings(commandLine);
913925
return {
914926
options,
915927
fileNames,
916-
projectReferences,
917928
errors
918929
};
919930

@@ -926,15 +937,15 @@ namespace ts {
926937
parseResponseFile(s.slice(1));
927938
}
928939
else if (s.charCodeAt(0) === CharacterCodes.minus) {
929-
const opt = getOptionFromName(s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1), /*allowShort*/ true);
940+
const opt = getOptionDeclarationFromName(getOptionNameMap, s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1), /*allowShort*/ true);
930941
if (opt) {
931942
if (opt.isTSConfigOnly) {
932943
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name));
933944
}
934945
else {
935946
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
936947
if (!args[i] && opt.type !== "boolean") {
937-
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_expects_an_argument, opt.name));
948+
errors.push(createCompilerDiagnostic(optionTypeMismatchDiagnostic, opt.name));
938949
}
939950

940951
switch (opt.type) {
@@ -971,7 +982,7 @@ namespace ts {
971982
}
972983
}
973984
else {
974-
errors.push(createCompilerDiagnostic(Diagnostics.Unknown_compiler_option_0, s));
985+
errors.push(createCompilerDiagnostic(unknownOptionDiagnostic, s));
975986
}
976987
}
977988
else {
@@ -1014,13 +1025,19 @@ namespace ts {
10141025
}
10151026
}
10161027

1028+
export function parseCommandLine(commandLine: ReadonlyArray<string>, readFile?: (path: string) => string | undefined): ParsedCommandLine {
1029+
return parseCommandLineWorker(getOptionNameMap, [
1030+
Diagnostics.Unknown_compiler_option_0,
1031+
Diagnostics.Compiler_option_0_expects_an_argument
1032+
], commandLine, readFile);
1033+
}
1034+
10171035
/** @internal */
10181036
export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined {
10191037
return getOptionDeclarationFromName(getOptionNameMap, optionName, allowShort);
10201038
}
10211039

1022-
/*@internal*/
1023-
export function getOptionDeclarationFromName(getOptionNameMap: () => OptionNameMap, optionName: string, allowShort = false): CommandLineOption | undefined {
1040+
function getOptionDeclarationFromName(getOptionNameMap: () => OptionNameMap, optionName: string, allowShort = false): CommandLineOption | undefined {
10241041
optionName = optionName.toLowerCase();
10251042
const { optionNameMap, shortOptionNames } = getOptionNameMap();
10261043
// Try to translate short option names to their full equivalents.
@@ -1044,25 +1061,11 @@ namespace ts {
10441061
export function parseBuildCommand(args: string[]): ParsedBuildCommand {
10451062
let buildOptionNameMap: OptionNameMap | undefined;
10461063
const returnBuildOptionNameMap = () => (buildOptionNameMap || (buildOptionNameMap = createOptionNameMap(buildOpts)));
1047-
1048-
const buildOptions: BuildOptions = {};
1049-
const projects: string[] = [];
1050-
let errors: Diagnostic[] | undefined;
1051-
for (const arg of args) {
1052-
if (arg.charCodeAt(0) === CharacterCodes.minus) {
1053-
const opt = getOptionDeclarationFromName(returnBuildOptionNameMap, arg.slice(arg.charCodeAt(1) === CharacterCodes.minus ? 2 : 1), /*allowShort*/ true);
1054-
if (opt) {
1055-
buildOptions[opt.name as keyof BuildOptions] = true;
1056-
}
1057-
else {
1058-
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Unknown_build_option_0, arg));
1059-
}
1060-
}
1061-
else {
1062-
// Not a flag, parse as filename
1063-
projects.push(arg);
1064-
}
1065-
}
1064+
const { options, fileNames: projects, errors } = parseCommandLineWorker(returnBuildOptionNameMap, [
1065+
Diagnostics.Unknown_build_option_0,
1066+
Diagnostics.Build_option_0_requires_a_value_of_type_1
1067+
], args);
1068+
const buildOptions = options as BuildOptions;
10661069

10671070
if (projects.length === 0) {
10681071
// tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ."
@@ -1071,19 +1074,19 @@ namespace ts {
10711074

10721075
// Nonsensical combinations
10731076
if (buildOptions.clean && buildOptions.force) {
1074-
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force"));
1077+
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force"));
10751078
}
10761079
if (buildOptions.clean && buildOptions.verbose) {
1077-
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose"));
1080+
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose"));
10781081
}
10791082
if (buildOptions.clean && buildOptions.watch) {
1080-
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch"));
1083+
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch"));
10811084
}
10821085
if (buildOptions.watch && buildOptions.dry) {
1083-
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"));
1086+
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"));
10841087
}
10851088

1086-
return { buildOptions, projects, errors: errors || emptyArray };
1089+
return { buildOptions, projects, errors };
10871090
}
10881091

10891092
function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string {

src/compiler/diagnosticMessages.json

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2948,7 +2948,10 @@
29482948
"category": "Error",
29492949
"code": 5072
29502950
},
2951-
2951+
"Build option '{0}' requires a value of type {1}.": {
2952+
"category": "Error",
2953+
"code": 5073
2954+
},
29522955

29532956
"Generates a sourcemap for each corresponding '.d.ts' file.": {
29542957
"category": "Message",
@@ -4640,7 +4643,7 @@
46404643
"category": "Message",
46414644
"code": 95062
46424645
},
4643-
4646+
46444647
"Add missing enum member '{0}'": {
46454648
"category": "Message",
46464649
"code": 95063
@@ -4649,12 +4652,12 @@
46494652
"category": "Message",
46504653
"code": 95064
46514654
},
4652-
"Convert to async function":{
4655+
"Convert to async function": {
46534656
"category": "Message",
4654-
"code": 95065
4657+
"code": 95065
46554658
},
46564659
"Convert all to async functions": {
4657-
"category": "Message",
4658-
"code": 95066
4660+
"category": "Message",
4661+
"code": 95066
46594662
}
46604663
}

src/compiler/tsbuild.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@ namespace ts {
1717
referencingProjectsMap: ConfigFileMap<ConfigFileMap<boolean>>;
1818
}
1919

20-
export interface BuildOptions {
20+
export interface BuildOptions extends OptionsBase {
2121
dry?: boolean;
2222
force?: boolean;
2323
verbose?: boolean;
24+
2425
/*@internal*/ clean?: boolean;
2526
/*@internal*/ watch?: boolean;
2627
/*@internal*/ help?: boolean;
28+
2729
preserveWatchOutput?: boolean;
30+
listEmittedFiles?: boolean;
31+
listFiles?: boolean;
2832
}
2933

3034
enum BuildResultFlags {
@@ -44,8 +48,9 @@ namespace ts {
4448
SyntaxErrors = 1 << 3,
4549
TypeErrors = 1 << 4,
4650
DeclarationEmitErrors = 1 << 5,
51+
EmitErrors = 1 << 6,
4752

48-
AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors
53+
AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors
4954
}
5055

5156
export enum UpToDateStatusType {
@@ -370,6 +375,14 @@ namespace ts {
370375
return host;
371376
}
372377

378+
function getCompilerOptionsOfBuildOptions(buildOptions: BuildOptions): CompilerOptions {
379+
const result = {} as CompilerOptions;
380+
commonOptionsWithBuild.forEach(option => {
381+
result[option.name] = buildOptions[option.name];
382+
});
383+
return result;
384+
}
385+
373386
/**
374387
* A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but
375388
* can dynamically add/remove other projects based on changes on the rootNames' references
@@ -384,6 +397,7 @@ namespace ts {
384397

385398
// State of the solution
386399
let options = defaultOptions;
400+
let baseCompilerOptions = getCompilerOptionsOfBuildOptions(options);
387401
type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic;
388402
const configFileCache = createFileMap<ConfigFileCacheEntry>(toPath);
389403
/** Map from output file name to its pre-build timestamp */
@@ -392,6 +406,7 @@ namespace ts {
392406
const projectStatus = createFileMap<UpToDateStatus>(toPath);
393407
const missingRoots = createMap<true>();
394408
let globalDependencyGraph: DependencyGraph | undefined;
409+
const writeFileName = (s: string) => host.trace && host.trace(s);
395410

396411
// Watch state
397412
const diagnostics = createFileMap<ReadonlyArray<Diagnostic>>(toPath);
@@ -430,6 +445,7 @@ namespace ts {
430445

431446
function resetBuildContext(opts = defaultOptions) {
432447
options = opts;
448+
baseCompilerOptions = getCompilerOptionsOfBuildOptions(options);
433449
configFileCache.clear();
434450
unchangedOutputs.clear();
435451
projectStatus.clear();
@@ -463,7 +479,7 @@ namespace ts {
463479

464480
let diagnostic: Diagnostic | undefined;
465481
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d;
466-
const parsed = getParsedCommandLineOfConfigFile(configFilePath, {}, parseConfigFileHost);
482+
const parsed = getParsedCommandLineOfConfigFile(configFilePath, baseCompilerOptions, parseConfigFileHost);
467483
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop;
468484
configFileCache.setValue(configFilePath, parsed || diagnostic!);
469485
return parsed;
@@ -475,7 +491,7 @@ namespace ts {
475491

476492
function reportWatchStatus(message: DiagnosticMessage, ...args: (string | number | undefined)[]) {
477493
if (hostWithWatch.onWatchStatusChange) {
478-
hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), host.getNewLine(), { preserveWatchOutput: options.preserveWatchOutput });
494+
hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), host.getNewLine(), baseCompilerOptions);
479495
}
480496
}
481497

@@ -1004,35 +1020,28 @@ namespace ts {
10041020
...program.getConfigFileParsingDiagnostics(),
10051021
...program.getSyntacticDiagnostics()];
10061022
if (syntaxDiagnostics.length) {
1007-
resultFlags |= BuildResultFlags.SyntaxErrors;
1008-
reportAndStoreErrors(proj, syntaxDiagnostics);
1009-
projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Syntactic errors" });
1010-
return resultFlags;
1023+
return buildErrors(syntaxDiagnostics, BuildResultFlags.SyntaxErrors, "Syntactic");
10111024
}
10121025

10131026
// Don't emit .d.ts if there are decl file errors
10141027
if (getEmitDeclarations(program.getCompilerOptions())) {
10151028
const declDiagnostics = program.getDeclarationDiagnostics();
10161029
if (declDiagnostics.length) {
1017-
resultFlags |= BuildResultFlags.DeclarationEmitErrors;
1018-
reportAndStoreErrors(proj, declDiagnostics);
1019-
projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Declaration file errors" });
1020-
return resultFlags;
1030+
return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file");
10211031
}
10221032
}
10231033

10241034
// Same as above but now for semantic diagnostics
10251035
const semanticDiagnostics = program.getSemanticDiagnostics();
10261036
if (semanticDiagnostics.length) {
1027-
resultFlags |= BuildResultFlags.TypeErrors;
1028-
reportAndStoreErrors(proj, semanticDiagnostics);
1029-
projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Semantic errors" });
1030-
return resultFlags;
1037+
return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic");
10311038
}
10321039

10331040
let newestDeclarationFileContentChangedTime = minimumDate;
10341041
let anyDtsChanged = false;
1035-
program.emit(/*targetSourceFile*/ undefined, (fileName, content, writeBom, onError) => {
1042+
let emitDiagnostics: Diagnostic[] | undefined;
1043+
const reportEmitDiagnostic = (d: Diagnostic) => (emitDiagnostics || (emitDiagnostics = [])).push(d);
1044+
emitFilesAndReportErrors(program, reportEmitDiagnostic, writeFileName, /*reportSummary*/ undefined, (fileName, content, writeBom, onError) => {
10361045
let priorChangeTime: Date | undefined;
10371046
if (!anyDtsChanged && isDeclarationFile(fileName)) {
10381047
// Check for unchanged .d.ts files
@@ -1052,12 +1061,23 @@ namespace ts {
10521061
}
10531062
});
10541063

1064+
if (emitDiagnostics) {
1065+
return buildErrors(emitDiagnostics, BuildResultFlags.EmitErrors, "Emit");
1066+
}
1067+
10551068
const status: UpToDateStatus = {
10561069
type: UpToDateStatusType.UpToDate,
10571070
newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime
10581071
};
10591072
projectStatus.setValue(proj, status);
10601073
return resultFlags;
1074+
1075+
function buildErrors(diagnostics: ReadonlyArray<Diagnostic>, errorFlags: BuildResultFlags, errorType: string) {
1076+
resultFlags |= errorFlags;
1077+
reportAndStoreErrors(proj, diagnostics);
1078+
projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` });
1079+
return resultFlags;
1080+
}
10611081
}
10621082

10631083
function updateOutputTimestamps(proj: ParsedCommandLine) {

0 commit comments

Comments
 (0)