More I/O fixes

Support for --charset command line option
File read and write errors are propagated into compiler diagnostics
emitFiles returns diagnostics in EmitResult
This commit is contained in:
Anders Hejlsberg 2014-07-16 10:49:11 -07:00
parent 546a8492f2
commit c1d0fd979d
12 changed files with 157 additions and 146 deletions

View File

@ -14,6 +14,7 @@ module ts {
};
var options: CommandLineOption[] = [
{ name: "charset", type: "string" },
{ name: "codepage", type: "number" },
{ name: "declaration", type: "boolean" },
{ name: "diagnostics", type: "boolean" },

View File

@ -279,7 +279,7 @@ module ts {
}
// Returns length of path root (i.e. length of "/", "x:/", "//server/share/")
function getRootLength(path: string): number {
export function getRootLength(path: string): number {
if (path.charCodeAt(0) === CharacterCodes.slash) {
if (path.charCodeAt(1) !== CharacterCodes.slash) return 1;
var p1 = path.indexOf("/", 2);

View File

@ -195,8 +195,7 @@ module ts {
Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." },
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },
Unsupported_file_encoding: { code: 5013, category: DiagnosticCategory.NoPrefix, key: "Unsupported file encoding." },
Could_not_write_file_0: { code: 5033, category: DiagnosticCategory.Error, key: "Could not write file '{0}'." },
Could_not_create_directory_0: { code: 5035, category: DiagnosticCategory.Error, key: "Could not create directory '{0}'." },
Could_not_write_file_0_Colon_1: { code: 5033, category: DiagnosticCategory.Error, key: "Could not write file '{0}': {1}" },
Option_mapRoot_cannot_be_specified_without_specifying_sourcemap_option: { code: 5038, category: DiagnosticCategory.Error, key: "Option mapRoot cannot be specified without specifying sourcemap option." },
Option_sourceRoot_cannot_be_specified_without_specifying_sourcemap_option: { code: 5039, category: DiagnosticCategory.Error, key: "Option sourceRoot cannot be specified without specifying sourcemap option." },
Variable_0_implicitly_has_an_1_type: { code: 7005, category: DiagnosticCategory.Error, key: "Variable '{0}' implicitly has an '{1}' type." },

View File

@ -11,8 +11,6 @@
"category": "Error",
"code": 6003
},
"Unrecognized escape sequence.": {
"category": "Error",
@ -779,14 +777,10 @@
"category": "NoPrefix",
"code": 5013
},
"Could not write file '{0}'.": {
"Could not write file '{0}': {1}": {
"category": "Error",
"code": 5033
},
"Could not create directory '{0}'.": {
"category": "Error",
"code": 5035
},
"Option mapRoot cannot be specified without specifying sourcemap option.": {
"category": "Error",
"code": 5038

View File

@ -69,29 +69,33 @@ module ts {
};
}
export function emitFiles(resolver: EmitResolver) {
export function emitFiles(resolver: EmitResolver): EmitResult {
var program = resolver.getProgram();
var compilerHost = program.getCompilerHost();
var compilerOptions = program.getCompilerOptions();
var shouldEmitDeclarations = resolver.shouldEmitDeclarations();
var sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap ? [] : undefined;
var diagnostics: Diagnostic[] = [];
forEach(program.getSourceFiles(), sourceFile => {
if (emitToOwnOutputFile(sourceFile, compilerOptions)) {
var jsFilePath = getOwnEmitOutputFilePath(sourceFile, program, ".js");
emitJavaScript(resolver, jsFilePath, sourceMapDataList, sourceFile);
emitJavaScript(resolver, jsFilePath, diagnostics, sourceMapDataList, sourceFile);
if (shouldEmitDeclarations) {
emitDeclarations(resolver, jsFilePath, sourceFile);
emitDeclarations(resolver, jsFilePath, diagnostics, sourceFile);
}
}
});
if (compilerOptions.out) {
emitJavaScript(resolver, compilerOptions.out, sourceMapDataList);
emitJavaScript(resolver, compilerOptions.out, diagnostics, sourceMapDataList);
if (shouldEmitDeclarations) {
emitDeclarations(resolver, compilerOptions.out);
emitDeclarations(resolver, compilerOptions.out, diagnostics);
}
}
return sourceMapDataList;
return {
errors: diagnostics,
sourceMaps: sourceMapDataList
};
}
interface EmitTextWriter extends TextWriter {
@ -174,7 +178,7 @@ module ts {
return text.substring(skipTrivia(text, node.pos), node.end);
}
function emitJavaScript(resolver: EmitResolver, jsFilePath: string, sourceMapDataList: SourceMapData[], root?: SourceFile) {
function emitJavaScript(resolver: EmitResolver, jsFilePath: string, diagnostics: Diagnostic[], sourceMapDataList: SourceMapData[], root?: SourceFile) {
var program = resolver.getProgram();
var compilerHost = program.getCompilerHost();
var compilerOptions = program.getCompilerOptions();
@ -221,6 +225,12 @@ module ts {
/** Sourcemap data that will get encoded */
var sourceMapData: SourceMapData;
function writeFile(filename: string, data: string) {
compilerHost.writeFile(filename, data, hostErrorMessage => {
diagnostics.push(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, filename, hostErrorMessage));
});
}
function initializeEmitterWithSourceMaps() {
var sourceMapDir: string; // The directory in which sourcemap will be
@ -442,7 +452,7 @@ module ts {
function writeJavaScriptAndSourceMapFile(emitOutput: string) {
// Write source map file
encodeLastRecordedSourceMapSpan();
compilerHost.writeFile(sourceMapData.sourceMapFilePath, JSON.stringify({
writeFile(sourceMapData.sourceMapFilePath, JSON.stringify({
version: 3,
file: sourceMapData.sourceMapFile,
sourceRoot: sourceMapData.sourceMapSourceRoot,
@ -527,7 +537,7 @@ module ts {
}
function writeJavaScriptFile(emitOutput: string) {
compilerHost.writeFile(jsFilePath, emitOutput);
writeFile(jsFilePath, emitOutput);
}
function emitTokenText(tokenKind: SyntaxKind, startPos: number, emitFn?: () => void) {
@ -1835,7 +1845,7 @@ module ts {
writeEmittedFiles(writer.getText());
}
function emitDeclarations(resolver: EmitResolver, jsFilePath: string, root?: SourceFile) {
function emitDeclarations(resolver: EmitResolver, jsFilePath: string, diagnostics: Diagnostic[], root?: SourceFile) {
var program = resolver.getProgram();
var compilerOptions = program.getCompilerOptions();
var compilerHost = program.getCompilerHost();
@ -1847,6 +1857,13 @@ module ts {
var decreaseIndent = writer.decreaseIndent;
var enclosingDeclaration: Node;
function writeFile(filename: string, data: string) {
compilerHost.writeFile(filename, data, hostErrorMessage => {
diagnostics.push(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, filename, hostErrorMessage));
});
}
function emitLines(nodes: Node[]) {
for (var i = 0, n = nodes.length; i < n; i++) {
emitNode(nodes[i]);
@ -2310,6 +2327,6 @@ module ts {
});
}
compilerHost.writeFile(getModuleNameFromFilename(jsFilePath) + ".d.ts", referencePathsOutput + writer.getText());
writeFile(getModuleNameFromFilename(jsFilePath) + ".d.ts", referencePathsOutput + writer.getText());
}
}

View File

@ -2989,7 +2989,10 @@ module ts {
}
else {
// If we haven't, read the file.
file = host.getSourceFile(filename, options.target);
file = host.getSourceFile(filename, options.target, hostErrorMessage => {
errors.push(createFileDiagnostic(refFile, refStart, refLength,
Diagnostics.Cannot_read_file_0_Colon_1, filename, hostErrorMessage));
});
if (file) {
filesByName[host.getCanonicalFileName(filename)] = file;
seenNoDefaultLib = seenNoDefaultLib || file.hasNoDefaultLib;

View File

@ -1,11 +1,13 @@
/// <reference path="diagnosticInformationMap.generated.ts"/>
interface System {
args: string[];
newLine: string;
useCaseSensitiveFileNames: boolean;
write(s: string): void;
writeErr(s: string): void;
readFile(fileName: string): string;
writeFile(fileName: string, data: string): boolean;
readFile(fileName: string, encoding?: string): string;
writeFile(fileName: string, data: string): void;
resolvePath(path: string): string;
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
@ -28,61 +30,63 @@ var sys: System = (function () {
var fso = new ActiveXObject("Scripting.FileSystemObject");
var fileStream = new ActiveXObject("ADODB.Stream");
fileStream.Type = 2;
var binaryStream = new ActiveXObject("ADODB.Stream");
binaryStream.Type = 1;
var args: string[] = [];
for (var i = 0; i < WScript.Arguments.length; i++) {
args[i] = WScript.Arguments.Item(i);
}
function readFile(fileName: string): string {
if (fso.FileExists(fileName)) {
fileStream.Open();
try {
// Load file in binary mode to ensure no byte order mark is added
fileStream.Type = 1;
function readFile(fileName: string, encoding?: string): string {
if (!fso.FileExists(fileName)) {
return undefined;
}
fileStream.Open();
try {
if (encoding) {
fileStream.Charset = encoding;
fileStream.LoadFromFile(fileName);
// Read the first two bytes into a string with no interpretation
fileStream.Type = 2;
}
else {
// Load file and read the first two bytes into a string with no interpretation
fileStream.Charset = "x-ansi";
fileStream.LoadFromFile(fileName);
var bom = fileStream.ReadText(2) || "";
// Position must be at 0 before encoding can be changed
fileStream.Position = 0;
// [0xFF,0xFE] and [0xFE,0xFF] mean utf-16 (little or big endian), otherwise default to utf-8
fileStream.Charset = bom.length >= 2 && (bom.charCodeAt(0) === 0xFF && bom.charCodeAt(1) === 0xFE || bom.charCodeAt(0) === 0xFE && bom.charCodeAt(1) === 0xFF) ? "unicode" : "utf-8";
// ReadText method always strips byte order mark from resulting string
var result = fileStream.ReadText();
fileStream.Close();
return result;
}
catch (e) {
fileStream.Close();
}
// ReadText method always strips byte order mark from resulting string
return fileStream.ReadText();
}
catch (e) {
throw e.number === -2147024809 ? new Error(ts.Diagnostics.Unsupported_file_encoding.key) : e;
}
finally {
fileStream.Close();
}
return undefined;
}
function writeFile(fileName: string, data: string): boolean {
function writeFile(fileName: string, data: string): void {
fileStream.Open();
binaryStream.Open();
try {
fileStream.Type = 2;
// Write characters in UTF-8 encoding
fileStream.Charset = "utf-8";
fileStream.WriteText(data);
// Skip byte order mark and copy remaining text to binary stream
binaryStream.Type = 1;
// Skip byte order mark and copy remaining data to binary stream
fileStream.Position = 3;
fileStream.CopyTo(binaryStream);
binaryStream.SaveToFile(fileName, 2 /*overwrite*/);
binaryStream.Close();
fileStream.Close();
return true;
}
catch (e) {
finally {
binaryStream.Close();
fileStream.Close();
}
return false;
}
return {
@ -134,42 +138,37 @@ var sys: System = (function () {
// win32\win64 are case insensitive platforms, MacOS (darwin) by default is also case insensitive
var useCaseSensitiveFileNames = platform !== "win32" && platform !== "win64" && platform !== "darwin";
function readFile(fileName: string): string {
if (_fs.existsSync(fileName)) {
try {
var buffer = _fs.readFileSync(fileName);
var len = buffer.length;
if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
len &= ~1;
for (var i = 0; i < len; i += 2) {
var temp = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = temp;
}
return buffer.toString("utf16le", 2);
}
if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
return buffer.toString("utf16le", 2);
}
if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
return buffer.toString("utf8", 3);
}
return buffer.toString("utf8");
}
catch (e) {
}
function readFile(fileName: string, encoding?: string): string {
if (!_fs.existsSync(fileName)) {
return undefined;
}
return undefined;
var buffer = _fs.readFileSync(fileName);
var len = buffer.length;
if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
// Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js,
// flip all byte pairs and treat as little endian.
len &= ~1;
for (var i = 0; i < len; i += 2) {
var temp = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = temp;
}
return buffer.toString("utf16le", 2);
}
if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
// Little endian UTF-16 byte order mark detected
return buffer.toString("utf16le", 2);
}
if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
// UTF-8 byte order mark detected
return buffer.toString("utf8", 3);
}
// Default is UTF-8 with no byte order mark
return buffer.toString("utf8");
}
function writeFile(fileName: string, data: string): boolean {
try {
_fs.writeFileSync(fileName, data, "utf8");
return true;
}
catch (e) {
}
return false;
function writeFile(fileName: string, data: string): void {
_fs.writeFileSync(fileName, data, "utf8");
}
return {
@ -222,4 +221,4 @@ var sys: System = (function () {
else {
return undefined; // Unsupported host
}
})();
})();

View File

@ -76,8 +76,6 @@ module ts {
return count;
}
// TODO (drosen): Make localize-friendly
var hasReportedErrors = false;
function reportErrors(errors: Diagnostic[]) {
for (var i = 0; i < errors.length; i++) {
var error = errors[i];
@ -90,7 +88,6 @@ module ts {
else {
sys.writeErr(error.messageText + sys.newLine);
}
hasReportedErrors = true;
}
}
@ -116,61 +113,56 @@ module ts {
reportDiagnostic(name, (time / 1000).toFixed(2) + "s");
}
function getSourceFile(filename: string, languageVersion: ScriptTarget): SourceFile {
var text = sys.readFile(filename);
return text ? createSourceFile(filename, text, languageVersion) : undefined;
}
function createCompilerHost(options: CompilerOptions): CompilerHost {
var currentDirectory: string;
var existingDirectories: Map<boolean> = {};
function writeFile(fileName: string, data: string) {
// TODO: Review this code for performance
//function ensureDirectoryStructure(directoryName: string) {
// if (directoryName) {
// if (!sys.directoryExists(directoryName)) {
// var parentDirectory = getDirectoryPath(directoryName);
// // If we arent at the root path ensure that the folder exists
// if (parentDirectory !== directoryName) {
// if (ensureDirectoryStructure(parentDirectory)) {
// // If parent directory was present, create the current directory
// try {
// sys.createDirectory(directoryName);
// }
// catch (e) {
// reportErrors([createCompilerDiagnostic(Diagnostics.Could_not_create_directory_0, [directoryName])]);
// return false;
// }
// }
// }
// }
// }
// return true;
//}
//// If parent directory structure is present create the file
//if (ensureDirectoryStructure(getDirectoryPath(normalizePath(fileName)))) {
// try {
// sys.writeFile(fileName, data);
// }
// catch (e) {
// reportErrors([createCompilerDiagnostic(Diagnostics.Could_not_write_file_0, [fileName])]);
// }
//}
if (!sys.writeFile(fileName, data)) {
reportErrors([createCompilerDiagnostic(Diagnostics.Could_not_write_file_0, [fileName])]);
function getSourceFile(filename: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile {
try {
var text = sys.readFile(filename, options.charset);
}
catch (e) {
if (onError) onError(e.message);
text = "";
}
return text !== undefined ? createSourceFile(filename, text, languageVersion) : undefined;
}
}
var currentDirectory: string;
function getCurrentDictory() {
currentDirectory = currentDirectory || sys.getCurrentDirectory();
return currentDirectory;
}
function writeFile(fileName: string, data: string, onError?: (message: string) => void) {
function directoryExists(directoryPath: string): boolean {
if (hasProperty(existingDirectories, directoryPath)) {
return true;
}
if (sys.directoryExists(directoryPath)) {
existingDirectories[directoryPath] = true;
return true;
}
return false;
}
function ensureDirectoriesExist(directoryPath: string) {
if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) {
var parentDirectory = getDirectoryPath(directoryPath);
ensureDirectoriesExist(parentDirectory);
sys.createDirectory(directoryPath);
}
}
try {
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
sys.writeFile(fileName, data);
}
catch (e) {
if (onError) onError(e.message);
}
}
function createCompilerHost(): CompilerHost {
return {
getSourceFile: getSourceFile,
getDefaultLibFilename: () => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), "lib.d.ts"),
writeFile: writeFile,
getCurrentDirectory: getCurrentDictory,
getCurrentDirectory: () => currentDirectory || sys.getCurrentDirectory(),
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
getCanonicalFileName: getCanonicalFileName
};
@ -201,7 +193,7 @@ module ts {
}
var parseStart = new Date().getTime();
var program = createProgram(cmds.filenames, cmds.options, createCompilerHost());
var program = createProgram(cmds.filenames, cmds.options, createCompilerHost(cmds.options));
var bindStart = new Date().getTime();
var errors = program.getDiagnostics();
if (errors.length) {
@ -212,10 +204,11 @@ module ts {
else {
var checker = program.getTypeChecker();
var checkStart = new Date().getTime();
errors = checker.getDiagnostics();
var semanticErrors = checker.getDiagnostics();
var emitStart = new Date().getTime();
checker.emitFiles();
var emitErrors = checker.emitFiles().errors;
var reportStart = new Date().getTime();
errors = concatenate(semanticErrors, emitErrors);
}
reportErrors(errors);
@ -232,7 +225,7 @@ module ts {
reportDiagnosticTime("Emit time", reportStart - emitStart);
reportDiagnosticTime("Total time", reportStart - parseStart);
}
return hasReportedErrors ? 1 : 0;
return errors.length ? 1 : 0;
}
}

View File

@ -566,7 +566,12 @@ module ts {
/** Raw source map spans that were encoded into the sourceMapMappings*/
sourceMapDecodedMappings: SourceMapSpan[];
}
export interface EmitResult {
errors: Diagnostic[];
sourceMaps: SourceMapData[]; // Array of sourceMapData if compiler emitted sourcemaps
}
export interface TypeChecker {
getProgram(): Program;
getDiagnostics(sourceFile?: SourceFile): Diagnostic[];
@ -576,7 +581,7 @@ module ts {
getSymbolCount(): number;
getTypeCount(): number;
checkProgram(): void;
emitFiles(): SourceMapData[]; // returns list of sourceMapData if compiler emitted sourcemaps
emitFiles(): EmitResult;
getSymbolOfNode(node: Node): Symbol;
getParentOfSymbol(symbol: Symbol): Symbol;
getTypeOfSymbol(symbol: Symbol): Type;
@ -896,6 +901,7 @@ module ts {
}
export interface CompilerOptions {
charset?: string;
codepage?: number;
declaration?: boolean;
diagnostics?: boolean;
@ -1083,10 +1089,10 @@ module ts {
}
export interface CompilerHost {
getSourceFile(filename: string, languageVersion: ScriptTarget): SourceFile;
getSourceFile(filename: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
getDefaultLibFilename(): string;
getCancellationToken? (): CancellationToken;
writeFile(filename: string, data: string): void;
writeFile(filename: string, data: string, onError?: (message: string) => void): void;
getCurrentDirectory(): string;
getCanonicalFileName(fileName: string): string;
useCaseSensitiveFileNames(): boolean;

View File

@ -738,7 +738,7 @@ module Harness {
// only emit if there weren't parse errors
var sourceMapData: ts.SourceMapData[];
if (!hadParseErrors) {
sourceMapData = checker.emitFiles();
sourceMapData = checker.emitFiles().sourceMaps;
}
var errors: MinimalDiagnostic[] = [];

View File

@ -190,9 +190,8 @@ module Playback {
return areSame(left, right) || areSame(wrapper.resolvePath(left), right) || areSame(left, wrapper.resolvePath(right)) || areSame(wrapper.resolvePath(left), wrapper.resolvePath(right));
}
function noOpReplay(name: string): boolean {
function noOpReplay(name: string) {
console.log("Swallowed write operation during replay: " + name);
return true;
}
export function wrapSystem(underlying: System): PlaybackSystem {

View File

@ -232,7 +232,7 @@ class ProjectRunner extends RunnerBase {
if (!errors.length) {
var checker = program.getTypeChecker();
errors = checker.getDiagnostics();
sourceMapData = checker.emitFiles();
sourceMapData = checker.emitFiles().sourceMaps;
// Clean up source map data that will be used in baselining
if (sourceMapData) {