Merge pull request #24465 from RyanCavanaugh/tsbuild

"tsc -b" with minimal watch capabilities
This commit is contained in:
Ryan Cavanaugh
2018-06-10 05:15:53 +12:00
committed by GitHub
46 changed files with 2390 additions and 185 deletions
+127
View File
@@ -109,6 +109,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",
@@ -968,6 +976,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
+89
View File
@@ -3620,6 +3620,95 @@
"category": "Error",
"code": 6309
},
"Project '{0}' is out of date because oldest output '{1}' is older than newest input '{2}'": {
"category": "Message",
"code": 6350
},
"Project '{0}' is up to date because newest input '{1}' is older than oldest output '{2}'": {
"category": "Message",
"code": 6351
},
"Project '{0}' is out of date because output file '{1}' does not exist": {
"category": "Message",
"code": 6352
},
"Project '{0}' is out of date because its dependency '{1}' is out of date": {
"category": "Message",
"code": 6353
},
"Project '{0}' is up to date with .d.ts files from its dependencies": {
"category": "Message",
"code": 6354
},
"Projects in this build: {0}": {
"category": "Message",
"code": 6355
},
"A non-dry build would delete the following files: {0}": {
"category": "Message",
"code": 6356
},
"A non-dry build would build project '{0}'": {
"category": "Message",
"code": 6357
},
"Building project '{0}'...": {
"category": "Message",
"code": 6358
},
"Updating output timestamps of project '{0}'...": {
"category": "Message",
"code": 6359
},
"delete this - Project '{0}' is up to date because it was previously built": {
"category": "Message",
"code": 6360
},
"Project '{0}' is up to date": {
"category": "Message",
"code": 6361
},
"Skipping build of project '{0}' because its dependency '{1}' has errors": {
"category": "Message",
"code": 6362
},
"Project '{0}' can't be built because its dependency '{1}' has errors": {
"category": "Message",
"code": 6363
},
"Build one or more projects and their dependencies, if out of date": {
"category": "Message",
"code": 6364
},
"Delete the outputs of all projects": {
"category": "Message",
"code": 6365
},
"Enable verbose logging": {
"category": "Message",
"code": 6366
},
"Show what would be built (or deleted, if specified with '--clean')": {
"category": "Message",
"code": 6367
},
"Build all projects, including those that appear to be up to date": {
"category": "Message",
"code": 6368
},
"Option '--build' must be the first command line argument.": {
"category": "Error",
"code": 6369
},
"Options '{0}' and '{1}' cannot be combined.": {
"category": "Error",
"code": 6370
},
"Skipping clean because not all projects could be located": {
"category": "Error",
"code": 6371
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
+1 -1
View File
@@ -1026,7 +1026,7 @@ namespace ts {
// SyntaxKind.UnparsedSource
function emitUnparsedSource(unparsed: UnparsedSource) {
write(unparsed.text);
writer.rawWrite(unparsed.text);
}
//
+5 -2
View File
@@ -2587,16 +2587,19 @@ namespace ts {
return node;
}
export function createUnparsedSourceFile(text: string): UnparsedSource {
export function createUnparsedSourceFile(text: string, map?: string): UnparsedSource {
const node = <UnparsedSource>createNode(SyntaxKind.UnparsedSource);
node.text = text;
node.sourceMapText = map;
return node;
}
export function createInputFiles(javascript: string, declaration: string): InputFiles {
export function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles {
const node = <InputFiles>createNode(SyntaxKind.InputFiles);
node.javascriptText = javascript;
node.javascriptMapText = javascriptMapText;
node.declarationText = declaration;
node.declarationMapText = declarationMapText;
return node;
}
+25 -17
View File
@@ -189,7 +189,10 @@ namespace ts {
getEnvironmentVariable: name => sys.getEnvironmentVariable ? sys.getEnvironmentVariable(name) : "",
getDirectories: (path: string) => sys.getDirectories(path),
realpath,
readDirectory: (path, extensions, include, exclude, depth) => sys.readDirectory(path, extensions, include, exclude, depth)
readDirectory: (path, extensions, include, exclude, depth) => sys.readDirectory(path, extensions, include, exclude, depth),
getModifiedTime: sys.getModifiedTime && (path => sys.getModifiedTime!(path)),
setModifiedTime: sys.setModifiedTime && ((path, date) => sys.setModifiedTime!(path, date)),
deleteFile: sys.deleteFile && (path => sys.deleteFile!(path))
};
}
@@ -615,25 +618,27 @@ namespace ts {
// A parallel array to projectReferences storing the results of reading in the referenced tsconfig files
const resolvedProjectReferences: (ResolvedProjectReference | undefined)[] | undefined = projectReferences ? [] : undefined;
const projectReferenceRedirects: Map<string> = createMap();
if (projectReferences) {
for (const ref of projectReferences) {
const parsedRef = parseProjectReferenceConfigFile(ref);
resolvedProjectReferences!.push(parsedRef);
if (parsedRef) {
if (parsedRef.commandLine.options.outFile) {
const dtsOutfile = changeExtension(parsedRef.commandLine.options.outFile, ".d.ts");
processSourceFile(dtsOutfile, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
addProjectReferenceRedirects(parsedRef.commandLine, projectReferenceRedirects);
}
}
}
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
const structuralIsReused = tryReuseStructureFromOldProgram();
if (structuralIsReused !== StructureIsReused.Completely) {
processingDefaultLibFiles = [];
processingOtherFiles = [];
if (projectReferences) {
for (const ref of projectReferences) {
const parsedRef = parseProjectReferenceConfigFile(ref);
resolvedProjectReferences!.push(parsedRef);
if (parsedRef) {
if (parsedRef.commandLine.options.outFile) {
const dtsOutfile = changeExtension(parsedRef.commandLine.options.outFile, ".d.ts");
processSourceFile(dtsOutfile, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
addProjectReferenceRedirects(parsedRef.commandLine, projectReferenceRedirects);
}
}
}
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false));
// load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders
@@ -1021,7 +1026,7 @@ namespace ts {
for (const oldSourceFile of oldSourceFiles) {
let newSourceFile = host.getSourceFileByPath
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile)
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath || oldSourceFile.path, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile)
: host.getSourceFile(oldSourceFile.fileName, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217
if (!newSourceFile) {
@@ -1234,8 +1239,10 @@ namespace ts {
const dtsFilename = changeExtension(resolvedRefOpts.options.outFile, ".d.ts");
const js = host.readFile(resolvedRefOpts.options.outFile) || `/* Input file ${resolvedRefOpts.options.outFile} was missing */\r\n`;
const jsMap = host.readFile(resolvedRefOpts.options.outFile + ".map"); // TODO: try to read sourceMappingUrl comment from the js file
const dts = host.readFile(dtsFilename) || `/* Input file ${dtsFilename} was missing */\r\n`;
const node = createInputFiles(js, dts);
const dtsMap = host.readFile(dtsFilename + ".map");
const node = createInputFiles(js, dts, jsMap, dtsMap);
nodes.push(node);
}
}
@@ -2047,6 +2054,7 @@ namespace ts {
if (file) {
sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0);
file.path = path;
file.resolvedPath = toPath(fileName);
if (host.useCaseSensitiveFileNames()) {
const pathLowerCase = path.toLowerCase();
@@ -2781,7 +2789,7 @@ namespace ts {
/**
* Returns the target config filename of a project reference
*/
function resolveProjectReferencePath(host: CompilerHost, ref: ProjectReference): string | undefined {
export function resolveProjectReferencePath(host: CompilerHost, ref: ProjectReference): string | undefined {
if (!host.fileExists(ref.path)) {
return combinePaths(ref.path, "tsconfig.json");
}
+107 -11
View File
@@ -99,6 +99,10 @@ namespace ts {
let sourceMapDataList: SourceMapData[] | undefined;
let disabled: boolean = !(compilerOptions.sourceMap || compilerOptions.inlineSourceMap);
let completedSections: SourceMapSectionDefinition[];
let sectionStartLine: number;
let sectionStartColumn: number;
return {
initialize,
reset,
@@ -146,6 +150,9 @@ namespace ts {
lastEncodedNameIndex = 0;
// Initialize source map data
completedSections = [];
sectionStartLine = 1;
sectionStartColumn = 1;
sourceMapData = {
sourceMapFilePath,
jsSourceMappingURL: !compilerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined!, // TODO: GH#18217
@@ -214,6 +221,65 @@ namespace ts {
lastEncodedNameIndex = undefined;
sourceMapData = undefined!;
sourceMapDataList = undefined!;
completedSections = undefined!;
sectionStartLine = undefined!;
sectionStartColumn = undefined!;
}
interface SourceMapSection {
version: 3;
file: string;
sourceRoot?: string;
sources: string[];
names?: string[];
mappings: string;
sourcesContent?: string[];
sections?: undefined;
}
type SourceMapSectionDefinition =
| { offset: { line: number, column: number }, url: string } // Included for completeness
| { offset: { line: number, column: number }, map: SourceMap };
interface SectionalSourceMap {
version: 3;
file: string;
sections: SourceMapSectionDefinition[];
}
type SourceMap = SectionalSourceMap | SourceMapSection;
function captureSection(): SourceMapSection {
return {
version: 3,
file: sourceMapData.sourceMapFile,
sourceRoot: sourceMapData.sourceMapSourceRoot,
sources: sourceMapData.sourceMapSources,
names: sourceMapData.sourceMapNames,
mappings: sourceMapData.sourceMapMappings,
sourcesContent: sourceMapData.sourceMapSourcesContent,
};
}
function resetSectionalData(): void {
sourceMapData.sourceMapSources = [];
sourceMapData.sourceMapNames = [];
sourceMapData.sourceMapMappings = "";
sourceMapData.sourceMapSourcesContent = compilerOptions.inlineSources ? [] : undefined;
}
function generateMap(): SourceMap {
if (completedSections.length) {
captureSectionalSpanIfNeeded(/*reset*/ false);
return {
version: 3,
file: sourceMapData.sourceMapFile,
sections: completedSections
};
}
else {
return captureSection();
}
}
// Encoding for sourcemap span
@@ -284,8 +350,8 @@ namespace ts {
sourceLinePos.line++;
sourceLinePos.character++;
const emittedLine = writer.getLine();
const emittedColumn = writer.getColumn();
const emittedLine = writer.getLine() - sectionStartLine + 1;
const emittedColumn = emittedLine === 0 ? (writer.getColumn() - sectionStartColumn + 1) : writer.getColumn();
// If this location wasn't recorded or the location in source is going backwards, record the span
if (!lastRecordedSourceMapSpan ||
@@ -320,6 +386,15 @@ namespace ts {
}
}
function captureSectionalSpanIfNeeded(reset: boolean) {
if (lastRecordedSourceMapSpan && lastRecordedSourceMapSpan === lastEncodedSourceMapSpan) { // If we've recorded some spans, save them
completedSections.push({ offset: { line: sectionStartLine - 1, column: sectionStartColumn - 1 }, map: captureSection() });
if (reset) {
resetSectionalData();
}
}
}
/**
* Emits a node with possible leading and trailing source maps.
*
@@ -333,6 +408,35 @@ namespace ts {
}
if (node) {
if (isUnparsedSource(node) && node.sourceMapText !== undefined) {
captureSectionalSpanIfNeeded(/*reset*/ true);
const text = node.sourceMapText;
let parsed: {} | undefined;
try {
parsed = JSON.parse(text);
}
catch {
// empty
}
const offset = { line: writer.getLine() - 1, column: writer.getColumn() - 1 };
completedSections.push(parsed
? {
offset,
map: parsed as SourceMap
}
: {
offset,
// This is just passes the buck on sourcemaps we don't really understand, instead of issuing an error (which would be difficult this late)
url: `data:application/json;charset=utf-8;base64,${base64encode(sys, text)}`
}
);
const emitResult = emitCallback(hint, node);
sectionStartLine = writer.getLine();
sectionStartColumn = writer.getColumn();
lastRecordedSourceMapSpan = undefined!;
lastEncodedSourceMapSpan = defaultLastEncodedSourceMapSpan;
return emitResult;
}
const emitNode = node.emitNode;
const emitFlags = emitNode && emitNode.flags || EmitFlags.None;
const range = emitNode && emitNode.sourceMapRange;
@@ -460,15 +564,7 @@ namespace ts {
encodeLastRecordedSourceMapSpan();
return JSON.stringify({
version: 3,
file: sourceMapData.sourceMapFile,
sourceRoot: sourceMapData.sourceMapSourceRoot,
sources: sourceMapData.sourceMapSources,
names: sourceMapData.sourceMapNames,
mappings: sourceMapData.sourceMapMappings,
sourcesContent: sourceMapData.sourceMapSourcesContent,
});
return JSON.stringify(generateMap());
}
/**
+23
View File
@@ -433,6 +433,7 @@ namespace ts {
readFile(path: string, encoding?: string): string | undefined;
getFileSize?(path: string): number;
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
/**
* @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that
* use native OS file watching
@@ -448,6 +449,8 @@ namespace ts {
getDirectories(path: string): string[];
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
getModifiedTime?(path: string): Date;
setModifiedTime?(path: string, time: Date): void;
deleteFile?(path: string): void;
/**
* A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm)
*/
@@ -592,6 +595,8 @@ namespace ts {
},
readDirectory,
getModifiedTime,
setModifiedTime,
deleteFile,
createHash: _crypto ? createMD5HashUsingNativeCrypto : generateDjb2Hash,
createSHA256Hash: _crypto ? createSHA256Hash : undefined,
getMemoryUsage() {
@@ -1069,6 +1074,24 @@ namespace ts {
}
}
function setModifiedTime(path: string, time: Date) {
try {
_fs.utimesSync(path, time, time);
}
catch (e) {
return;
}
}
function deleteFile(path: string) {
try {
return _fs.unlinkSync(path);
}
catch (e) {
return;
}
}
/**
* djb2 hashing algorithm
* http://www.cse.yorku.ca/~oz/hash.html
+1 -1
View File
@@ -180,7 +180,7 @@ namespace ts {
}
), mapDefined(node.prepends, prepend => {
if (prepend.kind === SyntaxKind.InputFiles) {
return createUnparsedSourceFile(prepend.declarationText);
return createUnparsedSourceFile(prepend.declarationText, prepend.declarationMapText);
}
}));
bundle.syntheticFileReferences = [];
+1 -1
View File
@@ -100,7 +100,7 @@ namespace ts {
function transformBundle(node: Bundle) {
return createBundle(node.sourceFiles.map(transformSourceFile), mapDefined(node.prepends, prepend => {
if (prepend.kind === SyntaxKind.InputFiles) {
return createUnparsedSourceFile(prepend.javascriptText);
return createUnparsedSourceFile(prepend.javascriptText, prepend.javascriptMapText);
}
return prepend;
}));
File diff suppressed because it is too large Load Diff
+26 -123
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,9 +41,33 @@ 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.length > 0 && ((args[0].toLowerCase() === "--build") || (args[0].toLowerCase() === "-b"))) {
const reportDiag = createDiagnosticReporter(sys, /*pretty*/ true);
const report = (message: DiagnosticMessage, ...args: string[]) => reportDiag(createCompilerDiagnostic(message, ...args));
const buildHost: BuildHost = {
error: report,
verbose: report,
message: report,
errorDiagnostic: d => reportDiag(d)
};
return performBuild(args.slice(1), createCompilerHost({}), buildHost, sys);
}
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) {
@@ -74,7 +93,7 @@ namespace ts {
if (commandLine.options.help || commandLine.options.all) {
printVersion();
printHelp(!!commandLine.options.all);
printHelp(getOptionsForHelp(commandLine));
return sys.exit(ExitStatus.Success);
}
@@ -107,7 +126,7 @@ namespace ts {
if (commandLine.fileNames.length === 0 && !configFileName) {
printVersion();
printHelp(!!commandLine.options.all);
printHelp(getOptionsForHelp(commandLine));
return sys.exit(ExitStatus.Success);
}
@@ -271,122 +290,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"));
+2 -1
View File
@@ -47,6 +47,7 @@
"moduleSpecifiers.ts",
"watch.ts",
"commandLineParser.ts",
"tsc.ts"
"tsbuild.ts",
"tsc.ts",
]
}
+11
View File
@@ -2558,6 +2558,7 @@ namespace ts {
fileName: string;
/* @internal */ path: Path;
text: string;
/* @internal */ resolvedPath: Path;
/**
* If two source files are for the same version of the same package, one will redirect to the other.
@@ -2658,12 +2659,15 @@ namespace ts {
export interface InputFiles extends Node {
kind: SyntaxKind.InputFiles;
javascriptText: string;
javascriptMapText?: string;
declarationText: string;
declarationMapText?: string;
}
export interface UnparsedSource extends Node {
kind: SyntaxKind.UnparsedSource;
text: string;
sourceMapText?: string;
}
export interface JsonSourceFile extends SourceFile {
@@ -4297,6 +4301,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;
@@ -4819,6 +4826,10 @@ namespace ts {
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
createHash?(data: string): string;
getModifiedTime?(fileName: string): Date;
setModifiedTime?(fileName: string, date: Date): void;
deleteFile?(fileName: string): void;
}
/* @internal */
+22 -10
View File
@@ -2861,13 +2861,26 @@ namespace ts {
let lineCount: number;
let linePos: number;
function updateLineCountAndPosFor(s: string) {
const lineStartsOfS = computeLineStarts(s);
if (lineStartsOfS.length > 1) {
lineCount = lineCount + lineStartsOfS.length - 1;
linePos = output.length - s.length + last(lineStartsOfS);
lineStart = (linePos - output.length) === 0;
}
else {
lineStart = false;
}
}
function write(s: string) {
if (s && s.length) {
if (lineStart) {
output += getIndentString(indent);
s = getIndentString(indent) + s;
lineStart = false;
}
output += s;
updateLineCountAndPosFor(s);
}
}
@@ -2881,21 +2894,14 @@ namespace ts {
function rawWrite(s: string) {
if (s !== undefined) {
if (lineStart) {
lineStart = false;
}
output += s;
updateLineCountAndPosFor(s);
}
}
function writeLiteral(s: string) {
if (s && s.length) {
write(s);
const lineStartsOfS = computeLineStarts(s);
if (lineStartsOfS.length > 1) {
lineCount = lineCount + lineStartsOfS.length - 1;
linePos = output.length - s.length + last(lineStartsOfS);
}
}
}
@@ -2909,7 +2915,9 @@ namespace ts {
}
function writeTextOfNode(text: string, node: Node) {
write(getTextOfNodeFromSourceText(text, node));
const s = getTextOfNodeFromSourceText(text, node);
write(s);
updateLineCountAndPosFor(s);
}
reset();
@@ -5487,6 +5495,10 @@ namespace ts {
return node.kind === SyntaxKind.Bundle;
}
export function isUnparsedSource(node: Node): node is UnparsedSource {
return node.kind === SyntaxKind.UnparsedSource;
}
// JSDoc
export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression {
+21 -1
View File
@@ -51,6 +51,10 @@ namespace fakes {
this.vfs.writeFileSync(path, writeByteOrderMark ? utils.addUTF8ByteOrderMark(data) : data);
}
public deleteFile(path: string) {
this.vfs.unlinkSync(path);
}
public fileExists(path: string) {
const stats = this._getStats(path);
return stats ? stats.isFile() : false;
@@ -131,6 +135,10 @@ namespace fakes {
return stats ? stats.mtime : undefined!; // TODO: GH#18217
}
public setModifiedTime(path: string, time: Date) {
this.vfs.utimesSync(path, time, time);
}
public createHash(data: string): string {
return data;
}
@@ -244,6 +252,10 @@ namespace fakes {
return this.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
}
public deleteFile(fileName: string) {
this.sys.deleteFile(fileName);
}
public fileExists(fileName: string): boolean {
return this.sys.fileExists(fileName);
}
@@ -252,6 +264,14 @@ namespace fakes {
return this.sys.directoryExists(directoryName);
}
public getModifiedTime(fileName: string) {
return this.sys.getModifiedTime(fileName);
}
public setModifiedTime(fileName: string, time: Date) {
return this.sys.setModifiedTime(fileName, time);
}
public getDirectories(path: string): string[] {
return this.sys.getDirectories(path);
}
@@ -312,7 +332,7 @@ namespace fakes {
if (cacheKey) {
const meta = this.vfs.filemeta(canonicalFileName);
const sourceFileFromMetadata = meta.get(cacheKey) as ts.SourceFile | undefined;
if (sourceFileFromMetadata) {
if (sourceFileFromMetadata && sourceFileFromMetadata.getFullText() === content) {
this._sourceFiles.set(canonicalFileName, sourceFileFromMetadata);
return sourceFileFromMetadata;
}
+1
View File
@@ -53,6 +53,7 @@
"../compiler/resolutionCache.ts",
"../compiler/moduleSpecifiers.ts",
"../compiler/watch.ts",
"../compiler/tsbuild.ts",
"../compiler/commandLineParser.ts",
"../services/types.ts",
+425
View File
@@ -0,0 +1,425 @@
namespace ts {
let currentTime = 100;
let lastDiagnostics: Diagnostic[] = [];
const reportDiagnostic: DiagnosticReporter = diagnostic => lastDiagnostics.push(diagnostic);
const report = (message: DiagnosticMessage, ...args: string[]) => reportDiagnostic(createCompilerDiagnostic(message, ...args));
const buildHost: BuildHost = {
error: report,
verbose: report,
message: report,
errorDiagnostic: d => reportDiagnostic(d)
};
export namespace Sample1 {
tick();
const projFs = loadProjectFromDisk("../../tests/projects/sample1");
const allExpectedOutputs = ["/src/tests/index.js",
"/src/core/index.js", "/src/core/index.d.ts",
"/src/logic/index.js", "/src/logic/index.d.ts"];
describe("tsbuild - sanity check of clean build of 'sample1' project", () => {
it("can build the sample project 'sample1' without error", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*empty*/);
// Check for outputs. Not an exhaustive list
for (const output of allExpectedOutputs) {
assert(fs.existsSync(output), `Expect file ${output} to exist`);
}
});
});
describe("tsbuild - dry builds", () => {
it("doesn't write any files in a dry build", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0);
// Check for outputs to not be written. Not an exhaustive list
for (const output of allExpectedOutputs) {
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
}
});
it("indicates that it would skip builds during a dry build", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
let builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
tick();
clearDiagnostics();
builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date);
});
});
describe("tsbuild - clean builds", () => {
it("removes all files it built", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
// Verify they exist
for (const output of allExpectedOutputs) {
assert(fs.existsSync(output), `Expect file ${output} to exist`);
}
builder.cleanAllProjects();
// Verify they are gone
for (const output of allExpectedOutputs) {
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
}
// Subsequent clean shouldn't throw / etc
builder.cleanAllProjects();
});
});
describe("tsbuild - force builds", () => {
it("always builds under --force", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: true, verbose: false });
builder.buildAllProjects();
let currentTime = time();
checkOutputTimestamps(currentTime);
tick();
Debug.assert(time() !== currentTime, "Time moves on");
currentTime = time();
builder.buildAllProjects();
checkOutputTimestamps(currentTime);
function checkOutputTimestamps(expected: number) {
// Check timestamps
for (const output of allExpectedOutputs) {
const actual = fs.statSync(output).mtimeMs;
assert(actual === expected, `File ${output} has timestamp ${actual}, expected ${expected}`);
}
}
});
});
describe("tsbuild - can detect when and what to rebuild", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: true });
it("Builds the project", () => {
clearDiagnostics();
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0);
tick();
});
// All three projects are up to date
it("Detects that all projects are up to date", () => {
clearDiagnostics();
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2);
tick();
});
// Update a file in the leaf node (tests), only it should rebuild the last one
it("Only builds the leaf node project", () => {
clearDiagnostics();
fs.writeFileSync("/src/tests/index.ts", "const m = 10;");
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2,
Diagnostics.Building_project_0);
tick();
});
// Update a file in the parent (without affecting types), should get fast downstream builds
it("Detects type-only changes in upstream projects", () => {
clearDiagnostics();
replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET");
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies,
Diagnostics.Updating_output_timestamps_of_project_0,
Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies,
Diagnostics.Updating_output_timestamps_of_project_0);
});
});
describe("tsbuild - downstream-blocked compilations", () => {
it("won't build downstream projects if upstream projects have errors", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: true });
clearDiagnostics();
// Induce an error in the middle project
replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`);
builder.buildAllProjects();
assertDiagnosticMessages(
Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0,
Diagnostics.Property_0_does_not_exist_on_type_1,
Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors,
Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors
);
});
});
describe("tsbuild - project invalidation", () => {
it("invalidates projects correctly", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*empty*/);
// Update a timestamp in the middle project
tick();
touch(fs, "/src/logic/index.ts");
// Because we haven't reset the build context, the builder should assume there's nothing to do right now
const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic")!);
assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date");
// Rebuild this project
tick();
builder.invalidateProject("/src/logic");
builder.buildInvalidatedProjects();
// The file should be updated
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
// Build downstream projects should update 'tests', but not 'core'
tick();
builder.buildDependentInvalidatedProjects();
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt");
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
});
});
}
export namespace OutFile {
const outFileFs = loadProjectFromDisk("../../tests/projects/outfile-concat");
describe("tsbuild - baseline sectioned sourcemaps", () => {
const fs = outFileFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/third"], { dry: false, force: false, verbose: false });
clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*none*/);
const files = [
"/src/third/thirdjs/output/third-output.js",
"/src/third/thirdjs/output/third-output.js.map"
];
for (const file of files) {
it(`Generates files matching the baseline - ${file}`, () => {
Harness.Baseline.runBaseline(getBaseFileName(file), () => {
return fs.readFileSync(file, "utf-8");
});
});
}
it(`Generates files matching the baseline - file listing for outFile-concat`, () => {
Harness.Baseline.runBaseline("outfile-concat-fileListing.txt", () => {
return fs.getFileListing();
});
});
});
}
describe("tsbuild - graph-ordering", () => {
const fs = new vfs.FileSystem(false);
const host = new fakes.CompilerHost(fs);
const deps: [string, string][] = [
["A", "B"],
["B", "C"],
["A", "C"],
["B", "D"],
["C", "D"],
["C", "E"],
["F", "E"]
];
writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G"], deps);
it("orders the graph correctly - specify two roots", () => {
checkGraphOrdering(["A", "G"], ["A", "B", "C", "D", "E", "G"]);
});
it("orders the graph correctly - multiple parts of the same graph in various orders", () => {
checkGraphOrdering(["A"], ["A", "B", "C", "D", "E"]);
checkGraphOrdering(["A", "C", "D"], ["A", "B", "C", "D", "E"]);
checkGraphOrdering(["D", "C", "A"], ["A", "B", "C", "D", "E"]);
});
it("orders the graph correctly - other orderings", () => {
checkGraphOrdering(["F"], ["F", "E"]);
checkGraphOrdering(["E"], ["E"]);
checkGraphOrdering(["F", "C", "A"], ["A", "B", "C", "D", "E", "F"]);
});
function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[]) {
const builder = createSolutionBuilder(host, buildHost, rootNames, { dry: true, force: false, verbose: false });
const projFileNames = rootNames.map(getProjectFileName);
const graph = builder.getBuildGraph(projFileNames);
if (graph === undefined) throw new Error("Graph shouldn't be undefined");
assert.sameMembers(graph.buildQueue, expectedBuildSet.map(getProjectFileName));
for (const dep of deps) {
const child = getProjectFileName(dep[0]);
if (graph.buildQueue.indexOf(child) < 0) continue;
const parent = getProjectFileName(dep[1]);
assert.isAbove(graph.buildQueue.indexOf(child), graph.buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`);
}
}
function getProjectFileName(proj: string) {
return `/project/${proj}/tsconfig.json` as ResolvedConfigFileName;
}
function writeProjects(fileSystem: vfs.FileSystem, projectNames: string[], deps: [string, string][]): string[] {
const projFileNames: string[] = [];
for (const dep of deps) {
if (projectNames.indexOf(dep[0]) < 0) throw new Error(`Invalid dependency - project ${dep[0]} does not exist`);
if (projectNames.indexOf(dep[1]) < 0) throw new Error(`Invalid dependency - project ${dep[1]} does not exist`);
}
for (const proj of projectNames) {
fileSystem.mkdirpSync(`/project/${proj}`);
fileSystem.writeFileSync(`/project/${proj}/${proj}.ts`, "export {}");
const configFileName = getProjectFileName(proj);
const configContent = JSON.stringify({
compilerOptions: { composite: true },
files: [`./${proj}.ts`],
references: deps.filter(d => d[0] === proj).map(d => ({ path: `../${d[1]}` }))
}, undefined, 2);
fileSystem.writeFileSync(configFileName, configContent);
projFileNames.push(configFileName);
}
return projFileNames;
}
});
function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
if (old.indexOf(oldText) < 0) {
throw new Error(`Text "${oldText}" does not exist in file ${path}`);
}
const newContent = old.replace(oldText, newText);
fs.writeFileSync(path, newContent, "utf-8");
}
function assertDiagnosticMessages(...expected: DiagnosticMessage[]) {
const actual = lastDiagnostics.slice();
if (actual.length !== expected.length) {
assert.fail<any>(actual, expected, `Diagnostic arrays did not match - got\r\n${actual.map(a => " " + a.messageText).join("\r\n")}\r\nexpected\r\n${expected.map(e => " " + e.message).join("\r\n")}`);
}
for (let i = 0; i < actual.length; i++) {
if (actual[i].code !== expected[i].code) {
assert.fail(actual[i].messageText, expected[i].message, `Mismatched error code - expected diagnostic ${i} "${actual[i].messageText}" to match ${expected[i].message}`);
}
}
}
function clearDiagnostics() {
lastDiagnostics = [];
}
export function printDiagnostics(header = "== Diagnostics ==") {
const out = createDiagnosticReporter(sys);
sys.write(header + "\r\n");
for (const d of lastDiagnostics) {
out(d);
}
}
function tick() {
currentTime += 60_000;
}
function time() {
return currentTime;
}
function touch(fs: vfs.FileSystem, path: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
fs.utimesSync(path, new Date(time()), new Date(time()));
}
function loadProjectFromDisk(root: string): vfs.FileSystem {
const fs = new vfs.FileSystem(/*ignoreCase*/ false, { time });
const rootPath = resolvePath(__dirname, root);
loadFsMirror(fs, rootPath, "/src");
fs.mkdirpSync("/lib");
const libs = ["es5", "dom", "webworker.importscripts", "scripthost"];
for (const lib of libs) {
const content = Harness.IO.readFile(combinePaths(Harness.libFolder, `lib.${lib}.d.ts`));
if (content === undefined) {
throw new Error(`Failed to read lib ${lib}`);
}
fs.writeFileSync(`/lib/lib.${lib}.d.ts`, content);
}
fs.writeFileSync("/lib/lib.d.ts", Harness.IO.readFile(combinePaths(Harness.libFolder, "lib.d.ts"))!);
fs.meta.set("defaultLibLocation", "/lib");
fs.makeReadonly();
return fs;
}
function loadFsMirror(vfs: vfs.FileSystem, localRoot: string, virtualRoot: string) {
vfs.mkdirpSync(virtualRoot);
for (const path of Harness.IO.readDirectory(localRoot)) {
const file = getBaseFileName(path);
vfs.writeFileSync(virtualRoot + "/" + file, Harness.IO.readFile(localRoot + "/" + file)!);
}
for (const dir of Harness.IO.getDirectories(localRoot)) {
loadFsMirror(vfs, localRoot + "/" + dir, virtualRoot + "/" + dir);
}
}
}
+38 -9
View File
@@ -5,6 +5,11 @@ namespace vfs {
*/
export const builtFolder = "/.ts";
/**
* Posix-style path to additional mountable folders (./tests/projects in this repo)
*/
export const projectsFolder = "/.projects";
/**
* Posix-style path to additional test libraries
*/
@@ -348,10 +353,7 @@ namespace vfs {
if (!result.node) this._mkdir(result);
}
/**
* Print diagnostic information about the structure of the file system to the console.
*/
public debugPrint(): void {
public getFileListing(): string {
let result = "";
const printLinks = (dirname: string | undefined, links: collections.SortedMap<string, Inode>) => {
const iterator = collections.getIterator(links);
@@ -379,7 +381,14 @@ namespace vfs {
}
};
printLinks(/*dirname*/ undefined, this._getRootLinks());
console.log(result);
return result;
}
/**
* Print diagnostic information about the structure of the file system to the console.
*/
public debugPrint(): void {
console.log(this.getFileListing());
}
// POSIX API (aligns with NodeJS "fs" module API)
@@ -404,7 +413,25 @@ namespace vfs {
}
/**
* Get file status.
* Change file access times
*
* NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
*/
public utimesSync(path: string, atime: Date, mtime: Date) {
if (this.isReadonly) throw createIOError("EROFS");
if (!isFinite(+atime) || !isFinite(+mtime)) throw createIOError("EINVAL");
const entry = this._walk(this._resolve(path));
if (!entry || !entry.node) {
throw createIOError("ENOENT");
}
entry.node.atimeMs = +atime;
entry.node.mtimeMs = +mtime;
entry.node.ctimeMs = this.time();
}
/**
* Get file status. If `path` is a symbolic link, it is dereferenced.
*
* @link http://pubs.opengroup.org/onlinepubs/9699919799/functions/lstat.html
*
@@ -414,9 +441,10 @@ namespace vfs {
return this._stat(this._walk(this._resolve(path), /*noFollow*/ true));
}
private _stat(entry: WalkResult) {
const node = entry.node;
if (!node) throw createIOError("ENOENT");
if (!node) throw createIOError(`ENOENT`, entry.realpath);
return new Stats(
node.dev,
node.ino,
@@ -1127,8 +1155,8 @@ namespace vfs {
EROFS: "file system is read-only"
});
export function createIOError(code: keyof typeof IOErrorMessages) {
const err: NodeJS.ErrnoException = new Error(`${code}: ${IOErrorMessages[code]}`);
export function createIOError(code: keyof typeof IOErrorMessages, details = "") {
const err: NodeJS.ErrnoException = new Error(`${code}: ${IOErrorMessages[code]} ${details}`);
err.code = code;
if (Error.captureStackTrace) Error.captureStackTrace(err, createIOError);
return err;
@@ -1282,6 +1310,7 @@ namespace vfs {
files: {
[builtFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "built/local"), resolver),
[testLibFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "tests/lib"), resolver),
[projectsFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "tests/projects"), resolver),
[srcFolder]: {}
},
cwd: srcFolder,
+2 -2
View File
@@ -635,8 +635,8 @@ namespace ts.server {
return this.rootFiles;
}
return map(this.program.getSourceFiles(), sourceFile => {
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path);
Debug.assert(!!scriptInfo, "getScriptInfo", () => `scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' is missing.`);
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.resolvedPath || sourceFile.path);
Debug.assert(!!scriptInfo, "getScriptInfo", () => `scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' / '${sourceFile.resolvedPath}' is missing.`);
return scriptInfo!;
});
}
+1
View File
@@ -540,6 +540,7 @@ namespace ts {
public _declarationBrand: any;
public fileName: string;
public path: Path;
public resolvedPath: Path;
public text: string;
public scriptSnapshot: IScriptSnapshot;
public lineMap: ReadonlyArray<number>;
+15 -2
View File
@@ -1674,11 +1674,14 @@ declare namespace ts {
interface InputFiles extends Node {
kind: SyntaxKind.InputFiles;
javascriptText: string;
javascriptMapText?: string;
declarationText: string;
declarationMapText?: string;
}
interface UnparsedSource extends Node {
kind: SyntaxKind.UnparsedSource;
text: string;
sourceMapText?: string;
}
interface JsonSourceFile extends SourceFile {
statements: NodeArray<JsonObjectExpressionStatement>;
@@ -2661,6 +2664,9 @@ declare namespace ts {
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
getEnvironmentVariable?(name: string): string | undefined;
createHash?(data: string): string;
getModifiedTime?(fileName: string): Date;
setModifiedTime?(fileName: string, date: Date): void;
deleteFile?(fileName: string): void;
}
interface SourceMapRange extends TextRange {
source?: SourceMapSource;
@@ -3015,6 +3021,8 @@ declare namespace ts {
getDirectories(path: string): string[];
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
getModifiedTime?(path: string): Date;
setModifiedTime?(path: string, time: Date): void;
deleteFile?(path: string): void;
/**
* A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm)
*/
@@ -3380,6 +3388,7 @@ declare namespace ts {
function isEnumMember(node: Node): node is EnumMember;
function isSourceFile(node: Node): node is SourceFile;
function isBundle(node: Node): node is Bundle;
function isUnparsedSource(node: Node): node is UnparsedSource;
function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression;
function isJSDocAllType(node: JSDocAllType): node is JSDocAllType;
function isJSDocUnknownType(node: Node): node is JSDocUnknownType;
@@ -3831,8 +3840,8 @@ declare namespace ts {
function createCommaList(elements: ReadonlyArray<Expression>): CommaListExpression;
function updateCommaList(node: CommaListExpression, elements: ReadonlyArray<Expression>): CommaListExpression;
function createBundle(sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource | InputFiles>): Bundle;
function createUnparsedSourceFile(text: string): UnparsedSource;
function createInputFiles(javascript: string, declaration: string): InputFiles;
function createUnparsedSourceFile(text: string, map?: string): UnparsedSource;
function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles;
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource>): Bundle;
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>): CallExpression;
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>, param: ParameterDeclaration, paramValue: Expression): CallExpression;
@@ -4053,6 +4062,10 @@ declare namespace ts {
* @returns A 'Program' object.
*/
function createProgram(rootNames: ReadonlyArray<string>, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): Program;
/**
* Returns the target config filename of a project reference
*/
function resolveProjectReferencePath(host: CompilerHost, ref: ProjectReference): string | undefined;
}
declare namespace ts {
interface EmitOutput {
+15 -2
View File
@@ -1674,11 +1674,14 @@ declare namespace ts {
interface InputFiles extends Node {
kind: SyntaxKind.InputFiles;
javascriptText: string;
javascriptMapText?: string;
declarationText: string;
declarationMapText?: string;
}
interface UnparsedSource extends Node {
kind: SyntaxKind.UnparsedSource;
text: string;
sourceMapText?: string;
}
interface JsonSourceFile extends SourceFile {
statements: NodeArray<JsonObjectExpressionStatement>;
@@ -2661,6 +2664,9 @@ declare namespace ts {
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
getEnvironmentVariable?(name: string): string | undefined;
createHash?(data: string): string;
getModifiedTime?(fileName: string): Date;
setModifiedTime?(fileName: string, date: Date): void;
deleteFile?(fileName: string): void;
}
interface SourceMapRange extends TextRange {
source?: SourceMapSource;
@@ -3015,6 +3021,8 @@ declare namespace ts {
getDirectories(path: string): string[];
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
getModifiedTime?(path: string): Date;
setModifiedTime?(path: string, time: Date): void;
deleteFile?(path: string): void;
/**
* A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm)
*/
@@ -3380,6 +3388,7 @@ declare namespace ts {
function isEnumMember(node: Node): node is EnumMember;
function isSourceFile(node: Node): node is SourceFile;
function isBundle(node: Node): node is Bundle;
function isUnparsedSource(node: Node): node is UnparsedSource;
function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression;
function isJSDocAllType(node: JSDocAllType): node is JSDocAllType;
function isJSDocUnknownType(node: Node): node is JSDocUnknownType;
@@ -3831,8 +3840,8 @@ declare namespace ts {
function createCommaList(elements: ReadonlyArray<Expression>): CommaListExpression;
function updateCommaList(node: CommaListExpression, elements: ReadonlyArray<Expression>): CommaListExpression;
function createBundle(sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource | InputFiles>): Bundle;
function createUnparsedSourceFile(text: string): UnparsedSource;
function createInputFiles(javascript: string, declaration: string): InputFiles;
function createUnparsedSourceFile(text: string, map?: string): UnparsedSource;
function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles;
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource>): Bundle;
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>): CallExpression;
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>, param: ParameterDeclaration, paramValue: Expression): CallExpression;
@@ -4053,6 +4062,10 @@ declare namespace ts {
* @returns A 'Program' object.
*/
function createProgram(rootNames: ReadonlyArray<string>, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): Program;
/**
* Returns the target config filename of a project reference
*/
function resolveProjectReferencePath(host: CompilerHost, ref: ProjectReference): string | undefined;
}
declare namespace ts {
interface EmitOutput {
@@ -0,0 +1,43 @@
*/
/lib/
/lib/lib.d.ts
/lib/lib.dom.d.ts
/lib/lib.es5.d.ts
/lib/lib.scripthost.d.ts
/lib/lib.webworker.importscripts.d.ts
/src/
/src/2/
/src/2/second-output.d.ts
/src/2/second-output.d.ts.map
/src/2/second-output.js
/src/2/second-output.js.map
/src/first/
/src/first/bin/
/src/first/bin/first-output.d.ts
/src/first/bin/first-output.d.ts.map
/src/first/bin/first-output.js
/src/first/bin/first-output.js.map
/src/first/first_part1.ts
/src/first/first_part2.ts
/src/first/first_part3.ts
/src/first/tsconfig.json
/src/first_part1.ts
/src/first_part2.ts
/src/first_part3.ts
/src/second/
/src/second/second_part1.ts
/src/second/second_part2.ts
/src/second/tsconfig.json
/src/second_part1.ts
/src/second_part2.ts
/src/third/
/src/third/third_part1.ts
/src/third/thirdjs/
/src/third/thirdjs/output/
/src/third/thirdjs/output/third-output.d.ts
/src/third/thirdjs/output/third-output.d.ts.map
/src/third/thirdjs/output/third-output.js
/src/third/thirdjs/output/third-output.js.map
/src/third/tsconfig.json
/src/third_part1.ts
/src/tsconfig.json
@@ -0,0 +1,26 @@
var s = "Hello, world";
console.log(s);
console.log(f());
function f() {
return "JS does hoists";
}
//# sourceMappingURL=first-output.js.map
var N;
(function (N) {
function f() {
console.log('testing');
}
f();
})(N || (N = {}));
var C = (function () {
function C() {
}
C.prototype.doSomething = function () {
console.log("something got done");
};
return C;
}());
//# sourceMappingURL=second-output.js.map
var c = new C();
c.doSomething();
//# sourceMappingURL=third-output.js.map
@@ -0,0 +1 @@
{"version":3,"file":"third-output.js","sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"file":"first-output.js","sourceRoot":"","sources":["first_part1.ts","first_part2.ts","first_part3.ts"],"names":[],"mappings":"AAIA,IAAM,CAAC,GAAG,cAAc,CAAC;AAMzB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;ACVf,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;ACAjB;IACI,OAAO,gBAAgB,CAAC;AAC5B,CAAC"}},{"offset":{"line":7,"column":0},"map":{"version":3,"file":"second-output.js","sourceRoot":"","sources":["second_part1.ts","second_part2.ts"],"names":[],"mappings":"AAIA,IAAU,CAAC,CAMV;AAND,WAAU,CAAC;IACP;QACI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAED,CAAC,EAAE,CAAC;AACR,CAAC,EANS,CAAC,KAAD,CAAC,QAMV;ACVD;IAAA;IAIA,CAAC;IAHG,uBAAW,GAAX;QACI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACtC,CAAC;IACL,QAAC;AAAD,CAAC,AAJD,IAIC"}},{"offset":{"line":22,"column":41},"map":{"version":3,"file":"third-output.js","sourceRoot":"","sources":["third_part1.ts"],"names":[],"mappings":";AAAA,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;AAChB,CAAC,CAAC,WAAW,EAAE,CAAC"}}]}
+26
View File
@@ -0,0 +1,26 @@
var s = "Hello, world";
console.log(s);
console.log(f());
function f() {
return "JS does hoists";
}
//# sourceMappingURL=first-output.js.map
var N;
(function (N) {
function f() {
console.log('testing');
}
f();
})(N || (N = {}));
var C = (function () {
function C() {
}
C.prototype.doSomething = function () {
console.log("something got done");
};
return C;
}());
//# sourceMappingURL=second-output.js.map
var c = new C();
c.doSomething();
//# sourceMappingURL=third-output.js.map
@@ -0,0 +1 @@
{"version":3,"file":"third-output.js","sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"file":"first-output.js","sourceRoot":"","sources":["../first_part1.ts","../first_part2.ts","../first_part3.ts"],"names":[],"mappings":"AAIA,IAAM,CAAC,GAAG,cAAc,CAAC;AAMzB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;ACVf,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;ACAjB;IACI,OAAO,gBAAgB,CAAC;AAC5B,CAAC"}},{"offset":{"line":7,"column":0},"map":{"version":3,"file":"second-output.js","sourceRoot":"","sources":["../second/second_part1.ts","../second/second_part2.ts"],"names":[],"mappings":"AAIA,IAAU,CAAC,CAMV;AAND,WAAU,CAAC;IACP;QACI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAED,CAAC,EAAE,CAAC;AACR,CAAC,EANS,CAAC,KAAD,CAAC,QAMV;ACVD;IAAA;IAIA,CAAC;IAHG,uBAAW,GAAX;QACI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACtC,CAAC;IACL,QAAC;AAAD,CAAC,AAJD,IAIC"}},{"offset":{"line":22,"column":41},"map":{"version":3,"file":"third-output.js","sourceRoot":"","sources":["../../third_part1.ts"],"names":[],"mappings":";AAAA,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;AAChB,CAAC,CAAC,WAAW,EAAE,CAAC"}}]}
@@ -14,5 +14,5 @@ function foo() {
}
// Shouldn't see any errors down here
var y = {a} 1 };
</>;
</>;
}
@@ -27,7 +27,7 @@ define(["require", "exports", "react"], function (require, exports, React) {
// Should be OK
var MainMenu = function (props) { return (<div>
<h3>Main Menu</h3>
</div>); };
</div>); };
var App = function (_a) {
var children = _a.children;
return (<div>
@@ -0,0 +1,11 @@
interface TheFirst {
none: any;
}
const s = "Hello, world";
interface NoJsForHereEither {
none: any;
}
console.log(s);
@@ -0,0 +1 @@
console.log(f());
@@ -0,0 +1,3 @@
function f() {
return "JS does hoists";
}
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es5",
"composite": true,
"removeComments": true,
"strict": false,
"sourceMap": true,
"declarationMap": true,
"declaration": true,
"outFile": "./bin/first-output.js"
},
"references": [
]
}
@@ -0,0 +1,11 @@
namespace N {
// Comment text
}
namespace N {
function f() {
console.log('testing');
}
f();
}
@@ -0,0 +1,5 @@
class C {
doSomething() {
console.log("something got done");
}
}
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es5",
"composite": true,
"removeComments": true,
"strict": false,
"sourceMap": true,
"declarationMap": true,
"declaration": true,
"outFile": "../2/second-output.js"
},
"references": [
]
}
@@ -0,0 +1,2 @@
var c = new C();
c.doSomething();
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"composite": true,
"removeComments": true,
"strict": false,
"sourceMap": true,
"declarationMap": true,
"declaration": true,
"outFile": "./thirdjs/output/third-output.js"
},
"references": [
{ "path": "../first", "prepend": true },
{ "path": "../second", "prepend": true },
]
}
+3
View File
@@ -0,0 +1,3 @@
export const someString: string = "HELLO WORLD";
export function leftPad(s: string, n: number) { return s + n; }
export function multiply(a: number, b: number) { return a * b; }
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"composite": true,
"declaration": true
}
}
+4
View File
@@ -0,0 +1,4 @@
import * as c from '../core/index';
export function getSecondsInDay() {
return c.multiply(10, 15);
}
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"declaration": true
},
"references": [
{ "path": "../core" }
]
}
+5
View File
@@ -0,0 +1,5 @@
import * as c from '../core/index';
import * as logic from '../logic/index';
c.leftPad("", 10);
logic.getSecondsInDay();
@@ -0,0 +1,7 @@
{
"references": [
{ "path": "../core" },
{ "path": "../logic" }
],
"files": ["index.ts"]
}
+5
View File
@@ -0,0 +1,5 @@
import * as logic from '../logic';
export function run() {
console.log(logic.getSecondsInDay());
}
+5
View File
@@ -0,0 +1,5 @@
{
"references": [
{ "path": "../logic/index" }
]
}