diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 7a589fe61cb3b..fab614cd4b541 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -16,6 +16,37 @@ module ts { type: "boolean", description: Diagnostics.Generates_corresponding_d_ts_file, }, + { + name: "packageName", + type: "string", + experimental: true, + description: Diagnostics.Specifies_the_name_of_the_package, + paramType: Diagnostics.NAME + }, + { + name: "packageMain", + type: "string", + isFilePath: true, + experimental: true, + description: Diagnostics.Specifies_the_main_module_for_the_package, + paramType: Diagnostics.FILE + }, + { + name: "packageDeclaration", + type: "string", + isFilePath: true, + experimental: true, + description: Diagnostics.Specifies_the_output_path_for_the_package_declaration, + paramType: Diagnostics.FILE + }, + { + name: "packageDir", + type: "string", + isFilePath: true, + experimental: true, + description: Diagnostics.Specifies_the_root_directory_of_the_package, + paramType: Diagnostics.DIRECTORY + }, { name: "diagnostics", type: "boolean", diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9fe9747fb57b3..95a53bf49b52c 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -20,6 +20,12 @@ module ts { EqualTo = 0, GreaterThan = 1 } + + export const enum StringComparison { + Ordinal = 0, + IgnoreCase = 1, + CurrentCultureIgnoreCase = 2 + } export interface StringSet extends Map { } @@ -485,6 +491,38 @@ module ts { return normalized; } + + export function compareStrings(x: string, y: string, comparison?: StringComparison): Comparison { + if (x === y) return Comparison.EqualTo; + if (x === undefined) return Comparison.LessThan; + if (y === undefined) return Comparison.GreaterThan; + if (comparison === StringComparison.CurrentCultureIgnoreCase) { + x = x.toLocaleLowerCase(); + y = y.toLocaleLowerCase(); + } + else if (comparison === StringComparison.IgnoreCase) { + x = x.toLowerCase(); + y = y.toLowerCase(); + } + + return x === y ? Comparison.EqualTo : x < y ? Comparison.LessThan : Comparison.GreaterThan; + } + + export function comparePaths(path1: string, path2: string, currentDirectory: string, ignoreCase?: boolean): Comparison { + let pathComponents1 = getNormalizedPathComponents(path1, currentDirectory); + let pathComponents2 = getNormalizedPathComponents(path2, currentDirectory); + let sharedLength = Math.min(pathComponents1.length, pathComponents2.length); + for (let i = 0; i < sharedLength; i++) { + let component1 = pathComponents1[i]; + let component2 = pathComponents2[i]; + let result = compareStrings(component1, component2, ignoreCase ? StringComparison.IgnoreCase : StringComparison.Ordinal); + if (result !== Comparison.EqualTo) { + return result; + } + } + + return compareValues(pathComponents1.length, pathComponents2.length); + } export function normalizePath(path: string): string { path = normalizeSlashes(path); diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 5c705f1819045..d3ae0ca48f7f2 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -34,10 +34,20 @@ module ts { let diagnostics: Diagnostic[] = []; let jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, ".js"); emitDeclarations(host, resolver, diagnostics, jsFilePath, targetSourceFile); + + let packageDeclaration = host.getPackageDeclaration(); + if (packageDeclaration) { + let packageMain = host.getPackageMain(); + if (!targetSourceFile || + comparePaths(targetSourceFile.fileName, host.getPackageMain(), host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo) { + writePackageDeclarationFile(packageDeclaration, host, resolver, diagnostics); + } + } + return diagnostics; } - function emitDeclarations(host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[], jsFilePath: string, root?: SourceFile): DeclarationEmit { + function emitDeclarations(host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[], jsFilePath: string, root?: SourceFile, isPackageDeclaration?: boolean): DeclarationEmit { let newLine = host.getNewLine(); let compilerOptions = host.getCompilerOptions(); let languageVersion = compilerOptions.target || ScriptTarget.ES3; @@ -58,13 +68,38 @@ module ts { let moduleElementDeclarationEmitInfo: ModuleElementDeclarationEmitInfo[] = []; let asynchronousSubModuleDeclarationEmitInfo: ModuleElementDeclarationEmitInfo[]; + let packageMain = isPackageDeclaration ? host.getPackageMain() : undefined; // Contains the reference paths that needs to go in the declaration file. // Collecting this separately because reference paths need to be first thing in the declaration file // and we could be collecting these paths from multiple files into single one with --out option let referencePathsOutput = ""; + + if (isPackageDeclaration) { + // Emitting a package declaration, so emit all of the source declarations + let emittedReferencedFiles: SourceFile[] = []; + for (let sourceFile of sortSourceFiles(host.getSourceFiles())) { + if (!isDeclarationFile(sourceFile)) { + // Check what references need to be added + if (!compilerOptions.noResolve) { + for (let fileReference of sourceFile.referencedFiles) { + let referencedFile = tryResolveScriptReference(host, sourceFile, fileReference); - if (root) { + // If the reference file is a declaration file or an external module, emit that reference + if (referencedFile && (isDeclarationFile(referencedFile) && + !contains(emittedReferencedFiles, referencedFile))) { // If the file reference was not already emitted + writeReferencePath(referencedFile); + emittedReferencedFiles.push(referencedFile); + } + } + } + + writeLine(); + emitSourceFile(sourceFile); + } + } + } + else if (root) { // Emitting just a single file, so emit references in this file only if (!compilerOptions.noResolve) { let addedGlobalFileReference = false; @@ -83,9 +118,9 @@ module ts { } }); } - + emitSourceFile(root); - + // create asynchronous output for the importDeclarations if (moduleElementDeclarationEmitInfo.length) { let oldWriter = writer; @@ -104,11 +139,11 @@ module ts { else { // Emit references corresponding to this file let emittedReferencedFiles: SourceFile[] = []; - forEach(host.getSourceFiles(), sourceFile => { + for (let sourceFile of host.getSourceFiles()) { if (!isExternalModuleOrDeclarationFile(sourceFile)) { // Check what references need to be added if (!compilerOptions.noResolve) { - forEach(sourceFile.referencedFiles, fileReference => { + for (let fileReference of sourceFile.referencedFiles) { let referencedFile = tryResolveScriptReference(host, sourceFile, fileReference); // If the reference file is a declaration file or an external module, emit that reference @@ -118,14 +153,15 @@ module ts { writeReferencePath(referencedFile); emittedReferencedFiles.push(referencedFile); } - }); + } } + writeLine(); emitSourceFile(sourceFile); } - }); + } } - + return { reportedDeclarationError, moduleElementDeclarationEmitInfo, @@ -133,6 +169,33 @@ module ts { referencePathsOutput, } + function sortSourceFiles(sourceFiles: SourceFile[]) { + let indices = new Array(sourceFiles.length); + for (let i = 0; i < sourceFiles.length; ++i) indices[i] = i; + let currentDirectory = host.getCurrentDirectory(); + let ignoreCase = !host.useCaseSensitiveFileNames(); + indices.sort((left, right) => { + let leftFile = sourceFiles[left]; + if (comparePaths(leftFile.fileName, packageMain, currentDirectory, ignoreCase) === Comparison.EqualTo) { + return -1; + } + + let rightFile = sourceFiles[right]; + if (comparePaths(rightFile.fileName, packageMain, currentDirectory, ignoreCase) === Comparison.EqualTo) { + return +1; + } + + return left - right; + }); + + let sorted = new Array(sourceFiles.length); + for (let i = 0; i < sourceFiles.length; ++i) { + sorted[i] = sourceFiles[indices[i]]; + } + + return sorted; + } + function hasInternalAnnotation(range: CommentRange) { let text = currentSourceFile.text; let comment = text.substring(range.pos, range.end); @@ -439,7 +502,25 @@ module ts { function emitSourceFile(node: SourceFile) { currentSourceFile = node; enclosingDeclaration = node; + + if (isPackageDeclaration && isExternalModule(node)) { + // compute file name relative to main + let sourcePath = removeFileExtension(node.fileName); + let packageQualifiedModuleName = getPackageQualifiedPath(host, sourcePath, "."); + write("declare module \""); + write(escapeString(packageQualifiedModuleName)); + write("\" {"); + increaseIndent(); + writeLine(); + } + emitLines(node.statements); + + if (isPackageDeclaration && isExternalModule(node)) { + decreaseIndent(); + writeLine(); + write("}"); + } } // Return a temp variable name to be used in `export default` statements. @@ -563,7 +644,7 @@ module ts { function emitModuleElementDeclarationFlags(node: Node) { // If the node is parented in the current source file we need to emit export declare or just export - if (node.parent === currentSourceFile) { + if (node.parent === currentSourceFile && !(isPackageDeclaration && isExternalModule(currentSourceFile))) { // If the node is exported if (node.flags & NodeFlags.Export) { write("export "); @@ -607,7 +688,7 @@ module ts { } else { write("require("); - writeTextOfNode(currentSourceFile, getExternalModuleImportEqualsDeclarationExpression(node)); + emitModuleSpecifier(getExternalModuleImportEqualsDeclarationExpression(node)); write(");"); } writer.writeLine(); @@ -664,10 +745,30 @@ module ts { } write(" from "); } - writeTextOfNode(currentSourceFile, node.moduleSpecifier); + emitModuleSpecifier(node.moduleSpecifier); write(";"); writer.writeLine(); } + + function emitModuleSpecifier(node: Expression) { + if (isPackageDeclaration) { + let moduleNameText = (node).text; + let searchPath = getDirectoryPath(currentSourceFile.fileName); + let searchName = normalizePath(combinePaths(searchPath, moduleNameText)); + if (host.getSourceFile(searchName + ".ts") || host.getSourceFile(searchName + ".d.ts")) { + let packageQualifiedPath = getPackageQualifiedPath(host, moduleNameText, searchPath); + write("\""); + write(escapeString(packageQualifiedPath)); + write("\""); + } + else { + writeTextOfNode(currentSourceFile, node); + } + } + else { + writeTextOfNode(currentSourceFile, node); + } + } function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { if (node.propertyName) { @@ -686,7 +787,7 @@ module ts { // write each of these declarations asynchronously writeAsynchronousModuleElements(nodes); } - + function emitExportDeclaration(node: ExportDeclaration) { emitJsDocComments(node); write("export "); @@ -700,7 +801,7 @@ module ts { } if (node.moduleSpecifier) { write(" from "); - writeTextOfNode(currentSourceFile, node.moduleSpecifier); + emitModuleSpecifier(node.moduleSpecifier); } write(";"); writer.writeLine(); @@ -1561,6 +1662,57 @@ module ts { } } + function getPackageQualifiedPath(host: EmitHost, moduleName: string, basePath: string) { + let currentDirectory = host.getCurrentDirectory(); + let ignoreCase = !host.useCaseSensitiveFileNames(); + let packageRoot = host.getPackageDirectory(); + let modulePath = combinePaths(basePath, moduleName); + let packageRelativePath = getRelativePathToDirectoryOrUrl( + packageRoot, + modulePath, + currentDirectory, + host.getCanonicalFileName, + false); + + let compilerOptions = host.getCompilerOptions(); + let packageMain = host.getPackageMain(); + let packageAbsolutePath = getNormalizedAbsolutePath(packageRelativePath, packageRoot); + + if (comparePaths(packageAbsolutePath + ".ts", packageMain, currentDirectory, ignoreCase) === Comparison.EqualTo || + comparePaths(packageAbsolutePath + ".d.ts", packageMain, currentDirectory, ignoreCase) === Comparison.EqualTo) { + return compilerOptions.packageName; + } + + return combinePaths(compilerOptions.packageName, packageRelativePath); + } + + function getDeclarationOutput(synchronousDeclarationOutput: string, moduleElementDeclarationEmitInfo: ModuleElementDeclarationEmitInfo[]) { + let appliedSyncOutputPos = 0; + let declarationOutput = ""; + // apply asynchronous additions to the synchronous output + forEach(moduleElementDeclarationEmitInfo, aliasEmitInfo => { + if (aliasEmitInfo.asynchronousOutput) { + declarationOutput += synchronousDeclarationOutput.substring(appliedSyncOutputPos, aliasEmitInfo.outputPos); + declarationOutput += getDeclarationOutput(aliasEmitInfo.asynchronousOutput, aliasEmitInfo.subModuleElementDeclarationEmitInfo); + appliedSyncOutputPos = aliasEmitInfo.outputPos; + } + }); + declarationOutput += synchronousDeclarationOutput.substring(appliedSyncOutputPos); + return declarationOutput; + } + + /* @internal */ + export function writePackageDeclarationFile(dtsFilePath: string, host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[]) { + let compilerOptions = host.getCompilerOptions(); + let emitDeclarationResult = emitDeclarations(host, resolver, diagnostics, dtsFilePath, /*root*/ undefined, /*isPackageDeclaration*/ true); + if (!emitDeclarationResult.reportedDeclarationError) { + let declarationOutput = emitDeclarationResult.referencePathsOutput + + getDeclarationOutput(emitDeclarationResult.synchronousDeclarationOutput, emitDeclarationResult.moduleElementDeclarationEmitInfo); + + writeFile(host, diagnostics, dtsFilePath, declarationOutput, compilerOptions.emitBOM); + } + } + /* @internal */ export function writeDeclarationFile(jsFilePath: string, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[]) { let emitDeclarationResult = emitDeclarations(host, resolver, diagnostics, jsFilePath, sourceFile); @@ -1569,22 +1721,8 @@ module ts { if (!emitDeclarationResult.reportedDeclarationError) { let declarationOutput = emitDeclarationResult.referencePathsOutput + getDeclarationOutput(emitDeclarationResult.synchronousDeclarationOutput, emitDeclarationResult.moduleElementDeclarationEmitInfo); - writeFile(host, diagnostics, removeFileExtension(jsFilePath) + ".d.ts", declarationOutput, host.getCompilerOptions().emitBOM); - } - function getDeclarationOutput(synchronousDeclarationOutput: string, moduleElementDeclarationEmitInfo: ModuleElementDeclarationEmitInfo[]) { - let appliedSyncOutputPos = 0; - let declarationOutput = ""; - // apply asynchronous additions to the synchronous output - forEach(moduleElementDeclarationEmitInfo, aliasEmitInfo => { - if (aliasEmitInfo.asynchronousOutput) { - declarationOutput += synchronousDeclarationOutput.substring(appliedSyncOutputPos, aliasEmitInfo.outputPos); - declarationOutput += getDeclarationOutput(aliasEmitInfo.asynchronousOutput, aliasEmitInfo.subModuleElementDeclarationEmitInfo); - appliedSyncOutputPos = aliasEmitInfo.outputPos; - } - }); - declarationOutput += synchronousDeclarationOutput.substring(appliedSyncOutputPos); - return declarationOutput; + writeFile(host, diagnostics, removeFileExtension(jsFilePath) + ".d.ts", declarationOutput, host.getCompilerOptions().emitBOM); } } } \ No newline at end of file diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 32dc590eeecff..8bedba9e37965 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -453,11 +453,15 @@ module ts { Option_declaration_cannot_be_specified_with_option_separateCompilation: { code: 5044, category: DiagnosticCategory.Error, key: "Option 'declaration' cannot be specified with option 'separateCompilation'." }, Option_noEmitOnError_cannot_be_specified_with_option_separateCompilation: { code: 5045, category: DiagnosticCategory.Error, key: "Option 'noEmitOnError' cannot be specified with option 'separateCompilation'." }, Option_out_cannot_be_specified_with_option_separateCompilation: { code: 5046, category: DiagnosticCategory.Error, key: "Option 'out' cannot be specified with option 'separateCompilation'." }, - Option_separateCompilation_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES6_or_higher: { code: 5047, category: DiagnosticCategory.Error, key: "Option 'separateCompilation' can only be used when either option'--module' is provided or option 'target' is 'ES6' or higher." }, + Option_separateCompilation_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES6_or_higher: { code: 5047, category: DiagnosticCategory.Error, key: "Option 'separateCompilation' can only be used when either option 'module' is provided or option 'target' is 'ES6' or higher." }, Option_sourceMap_cannot_be_specified_with_option_inlineSourceMap: { code: 5048, category: DiagnosticCategory.Error, key: "Option 'sourceMap' cannot be specified with option 'inlineSourceMap'." }, Option_sourceRoot_cannot_be_specified_with_option_inlineSourceMap: { code: 5049, category: DiagnosticCategory.Error, key: "Option 'sourceRoot' cannot be specified with option 'inlineSourceMap'." }, Option_mapRoot_cannot_be_specified_with_option_inlineSourceMap: { code: 5050, category: DiagnosticCategory.Error, key: "Option 'mapRoot' cannot be specified with option 'inlineSourceMap'." }, Option_inlineSources_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided: { code: 5051, category: DiagnosticCategory.Error, key: "Option 'inlineSources' can only be used when either option '--inlineSourceMap' or option '--sourceMap' is provided." }, + Options_0_and_1_must_also_be_specified_with_option_2: { code: 5052, category: DiagnosticCategory.Error, key: "Options '{0}' and '{1}' must also be specified with option '{2}'." }, + Options_packageName_packageMain_and_packageDeclaration_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES6_or_higher: { code: 5054, category: DiagnosticCategory.Error, key: "Options 'packageName', 'packageMain', and 'packageDeclaration' can only be used when either option 'module' is provided or option 'target' is 'ES6' or higher." }, + Option_0_cannot_be_specified_with_option_separateCompilation: { code: 5055, category: DiagnosticCategory.Error, key: "Option '{0}' cannot be specified with option 'separateCompilation'." }, + Option_noEmit_cannot_be_specified_with_option_packageDeclaration: { code: 5056, category: DiagnosticCategory.Error, key: "Option 'noEmit' cannot be specified with option 'packageDeclaration'." }, Concatenate_and_emit_output_to_single_file: { code: 6001, category: DiagnosticCategory.Message, key: "Concatenate and emit output to single file." }, Generates_corresponding_d_ts_file: { code: 6002, category: DiagnosticCategory.Message, key: "Generates corresponding '.d.ts' file." }, Specifies_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations: { code: 6003, category: DiagnosticCategory.Message, key: "Specifies the location where debugger should locate map files instead of generated locations." }, @@ -507,6 +511,11 @@ module ts { Specifies_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix: { code: 6060, category: DiagnosticCategory.Message, key: "Specifies the end of line sequence to be used when emitting files: 'CRLF' (dos) or 'LF' (unix)." }, NEWLINE: { code: 6061, category: DiagnosticCategory.Message, key: "NEWLINE" }, Argument_for_newLine_option_must_be_CRLF_or_LF: { code: 6062, category: DiagnosticCategory.Error, key: "Argument for '--newLine' option must be 'CRLF' or 'LF'." }, + Specifies_the_main_module_for_the_package: { code: 6100, category: DiagnosticCategory.Message, key: "Specifies the main module for the package" }, + Specifies_the_name_of_the_package: { code: 6101, category: DiagnosticCategory.Message, key: "Specifies the name of the package" }, + Specifies_the_output_path_for_the_package_declaration: { code: 6102, category: DiagnosticCategory.Message, key: "Specifies the output path for the package declaration" }, + Specifies_the_root_directory_of_the_package: { code: 6103, category: DiagnosticCategory.Message, key: "Specifies the root directory of the package" }, + NAME: { code: 6104, category: DiagnosticCategory.Message, key: "NAME" }, Variable_0_implicitly_has_an_1_type: { code: 7005, category: DiagnosticCategory.Error, key: "Variable '{0}' implicitly has an '{1}' type." }, Parameter_0_implicitly_has_an_1_type: { code: 7006, category: DiagnosticCategory.Error, key: "Parameter '{0}' implicitly has an '{1}' type." }, Member_0_implicitly_has_an_1_type: { code: 7008, category: DiagnosticCategory.Error, key: "Member '{0}' implicitly has an '{1}' type." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a3dfd62d34e75..cc797caa51257 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1801,7 +1801,7 @@ "category": "Error", "code": 5046 }, - "Option 'separateCompilation' can only be used when either option'--module' is provided or option 'target' is 'ES6' or higher.": { + "Option 'separateCompilation' can only be used when either option 'module' is provided or option 'target' is 'ES6' or higher.": { "category": "Error", "code": 5047 }, @@ -1817,10 +1817,26 @@ "category": "Error", "code": 5050 }, - "Option 'inlineSources' can only be used when either option '--inlineSourceMap' or option '--sourceMap' is provided.": { + "Option 'inlineSources' can only be used when either option '--inlineSourceMap' or option '--sourceMap' is provided.": { "category": "Error", "code": 5051 }, + "Options '{0}' and '{1}' must also be specified with option '{2}'.": { + "category": "Error", + "code": 5052 + }, + "Options 'packageName', 'packageMain', and 'packageDeclaration' can only be used when either option 'module' is provided or option 'target' is 'ES6' or higher.": { + "category": "Error", + "code": 5054 + }, + "Option '{0}' cannot be specified with option 'separateCompilation'.": { + "category": "Error", + "code": 5055 + }, + "Option 'noEmit' cannot be specified with option 'packageDeclaration'.": { + "category": "Error", + "code": 5056 + }, "Concatenate and emit output to single file.": { "category": "Message", @@ -2018,7 +2034,26 @@ "category": "Error", "code": 6062 }, - + "Specifies the main module for the package": { + "category": "Message", + "code": 6100 + }, + "Specifies the name of the package": { + "category": "Message", + "code": 6101 + }, + "Specifies the output path for the package declaration": { + "category": "Message", + "code": 6102 + }, + "Specifies the root directory of the package": { + "category": "Message", + "code": 6103 + }, + "NAME": { + "category": "Message", + "code": 6104 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 59b6fcd7eb6c9..3e19be1c55d7f 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -53,6 +53,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { let sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined; let diagnostics: Diagnostic[] = []; let newLine = host.getNewLine(); + let packageDeclaration = host.getPackageDeclaration(); if (targetSourceFile === undefined) { forEach(host.getSourceFiles(), sourceFile => { @@ -65,6 +66,10 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { if (compilerOptions.out) { emitFile(compilerOptions.out); } + + if (packageDeclaration) { + writePackageDeclarationFile(packageDeclaration, host, resolver, diagnostics); + } } else { // targetSourceFile is specified (e.g calling emitter from language service or calling getSemanticDiagnostic from language service) @@ -75,6 +80,11 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { else if (!isDeclarationFile(targetSourceFile) && compilerOptions.out) { emitFile(compilerOptions.out); } + + if (packageDeclaration && + comparePaths(targetSourceFile.fileName, host.getPackageMain(), host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo) { + writePackageDeclarationFile(packageDeclaration, host, resolver, diagnostics); + } } // Sort and make the unique list of diagnostics diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 38634f1dea6a2..e500d4fb3559b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -28,6 +28,25 @@ module ts { } return undefined; } + + export function findPackageFile(searchPath: string): string { + let fileName = "package.json"; + while (true) { + if (sys.fileExists(fileName)) { + return fileName; + } + + let parentPath = getDirectoryPath(searchPath); + if (parentPath === searchPath) { + break; + } + + searchPath = parentPath; + fileName = "../" + fileName; + } + + return undefined; + } export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { let currentDirectory: string; @@ -93,6 +112,10 @@ module ts { } } } + + function getCurrentDirectory(): string { + return currentDirectory || (currentDirectory = sys.getCurrentDirectory()); + } let newLine = options.newLine === NewLineKind.CarriageReturnLineFeed ? carriageReturnLineFeed : @@ -103,7 +126,7 @@ module ts { getSourceFile, getDefaultLibFileName: options => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), getDefaultLibFileName(options)), writeFile, - getCurrentDirectory: () => currentDirectory || (currentDirectory = sys.getCurrentDirectory()), + getCurrentDirectory, useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine @@ -155,6 +178,9 @@ module ts { let commonSourceDirectory: string; let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; + let packageDirectory: string; + let packageMain: string; + let packageDeclaration: string; let start = new Date().getTime(); @@ -180,27 +206,32 @@ module ts { getCommonSourceDirectory: () => commonSourceDirectory, emit, getCurrentDirectory: () => host.getCurrentDirectory(), + getPackageDirectory: () => packageDirectory, getNodeCount: () => getDiagnosticsProducingTypeChecker().getNodeCount(), getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(), getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(), getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(), }; return program; - + function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { return { getCanonicalFileName: fileName => host.getCanonicalFileName(fileName), getCommonSourceDirectory: program.getCommonSourceDirectory, getCompilerOptions: program.getCompilerOptions, getCurrentDirectory: () => host.getCurrentDirectory(), + getPackageDirectory: () => packageDirectory, + getPackageMain: () => packageMain, + getPackageDeclaration: () => packageDeclaration, getNewLine: () => host.getNewLine(), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), getSourceFile: program.getSourceFile, getSourceFiles: program.getSourceFiles, writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError) => host.writeFile(fileName, data, writeByteOrderMark, onError)), }; } - + function getDiagnosticsProducingTypeChecker() { return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true)); } @@ -550,6 +581,18 @@ module ts { if (options.out) { diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_out_cannot_be_specified_with_option_separateCompilation)); } + + if (options.packageName) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_separateCompilation, "packageName")); + } + + if (options.packageMain) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_separateCompilation, "packageMain")); + } + + if (options.packageDeclaration) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_separateCompilation, "packageDeclaration")); + } } if (options.inlineSourceMap) { @@ -581,7 +624,7 @@ module ts { } return; } - + let languageVersion = options.target || ScriptTarget.ES3; let firstExternalModuleSourceFile = forEach(files, f => isExternalModule(f) ? f : undefined); @@ -631,6 +674,34 @@ module ts { } } + if (options.packageMain || options.packageName || options.packageDeclaration) { + if (!options.packageMain) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Options_0_and_1_must_also_be_specified_with_option_2, "packageName", "packageDeclaration", "packageMain")); + return; + } + if (!options.packageName) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Options_0_and_1_must_also_be_specified_with_option_2, "packageDeclaration", "packageMain", "packageName")); + return; + } + if (!options.packageDeclaration) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Options_0_and_1_must_also_be_specified_with_option_2, "packageMain", "packageName", "packageDeclaration")); + return; + } + if (options.module === ModuleKind.None && options.target < ScriptTarget.ES6) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Options_packageName_packageMain_and_packageDeclaration_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES6_or_higher)); + } + + if (options.packageDir) { + packageDirectory = getNormalizedAbsolutePath(options.packageDir, host.getCurrentDirectory()); + } + else { + packageDirectory = commonSourceDirectory ? commonSourceDirectory : host.getCurrentDirectory(); + } + + packageMain = getNormalizedAbsolutePath(options.packageMain, packageDirectory); + packageDeclaration = removeFileExtension(getNormalizedAbsolutePath(options.packageDeclaration, packageDirectory)) + ".d.ts"; + } + if (options.noEmit) { if (options.out || options.outDir) { diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_noEmit_cannot_be_specified_with_option_out_or_outDir)); @@ -639,6 +710,10 @@ module ts { if (options.declaration) { diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_noEmit_cannot_be_specified_with_option_declaration)); } + + if (options.packageDeclaration) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_noEmit_cannot_be_specified_with_option_packageDeclaration)); + } } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 86e680ca8b047..9691515201ad7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1032,6 +1032,7 @@ module ts { getCompilerOptions(): CompilerOptions; getSourceFile(fileName: string): SourceFile; getCurrentDirectory(): string; + /*@internal*/ getPackageDirectory(): string; } export interface ParseConfigHost { @@ -1678,6 +1679,10 @@ module ts { watch?: boolean; separateCompilation?: boolean; emitDecoratorMetadata?: boolean; + packageMain?: string; + packageName?: string; + packageDeclaration?: string; + packageDir?: string; /* @internal */ stripInternal?: boolean; [option: string]: string | number | boolean; } @@ -1727,6 +1732,13 @@ module ts { error?: DiagnosticMessage; // The error given when the argument does not fit a customized 'type' experimental?: boolean; } + + /* @internal */ + export interface PackageFile { + name: string; + main: string; + typings: string; + } /* @internal */ export const enum CharacterCodes { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 356337e1315cb..7d60984d16090 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -31,11 +31,12 @@ module ts { export interface EmitHost extends ScriptReferenceHost { getSourceFiles(): SourceFile[]; - getCommonSourceDirectory(): string; getCanonicalFileName(fileName: string): string; getNewLine(): string; - + useCaseSensitiveFileNames(): boolean; + getPackageMain(): string; + getPackageDeclaration(): string; writeFile: WriteFileCallback; } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 9ed515eb5b60b..b6d2cd6e210b7 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -45,10 +45,10 @@ module Utils { export function getExecutionEnvironment() { if (typeof WScript !== "undefined" && typeof ActiveXObject === "function") { return ExecutionEnvironment.CScript; - } else if (typeof window !== "undefined") { + } else if (typeof window !== "undefined") { return ExecutionEnvironment.Browser; - } else { - return ExecutionEnvironment.Node; + } else { + return ExecutionEnvironment.Node; } } @@ -1048,6 +1048,22 @@ module Harness { case 'declaration': options.declaration = setting.value === 'true'; break; + + case 'packagename': + options.packageName = setting.value; + break; + + case 'packagemain': + options.packageMain = setting.value; + break; + + case 'packagedeclaration': + options.packageDeclaration = setting.value; + break; + + case 'packagedir': + options.packageDir = setting.value; + break; case 'newline': if (setting.value.toLowerCase() === 'crlf') { @@ -1149,21 +1165,35 @@ module Harness { options?: ts.CompilerOptions, // Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file currentDirectory?: string) { - if (options.declaration && result.errors.length === 0 && result.declFilesCode.length !== result.files.length) { + let expectedDeclFileCount = 0; + if (options.declaration) { + expectedDeclFileCount += result.files.length; + } + if (options.packageDeclaration) { + expectedDeclFileCount++; + } + + if ((options.declaration || options.packageDeclaration) && result.errors.length === 0 && (result.declFilesCode.length !== expectedDeclFileCount)) { throw new Error('There were no errors and declFiles generated did not match number of js files generated'); } // if the .d.ts is non-empty, confirm it compiles correctly as well - if (options.declaration && result.errors.length === 0 && result.declFilesCode.length > 0) { + if ((options.declaration || options.packageDeclaration) && result.errors.length === 0 && result.declFilesCode.length > 0) { var declInputFiles: { unitName: string; content: string }[] = []; var declOtherFiles: { unitName: string; content: string }[] = []; var declResult: Harness.Compiler.CompilerResult; + if (options.packageDeclaration) { + let file = ts.forEach(result.declFilesCode, declFile => ts.comparePaths(declFile.fileName, options.packageDeclaration, result.currentDirectoryForProgram, true) === ts.Comparison.EqualTo ? declFile : undefined); + if (file) { + declInputFiles.push({ unitName: file.fileName, content: file.code }); + } + } ts.forEach(inputFiles, file => addDtsFile(file, declInputFiles)); ts.forEach(otherFiles, file => addDtsFile(file, declOtherFiles)); + this.compileFiles(declInputFiles, declOtherFiles, function (compileResult) { declResult = compileResult; }, settingsCallback, options, currentDirectory); - return { declInputFiles, declOtherFiles, declResult }; } @@ -1171,7 +1201,7 @@ module Harness { if (isDTS(file.unitName)) { dtsFiles.push(file); } - else if (isTS(file.unitName)) { + else if (isTS(file.unitName) && options.declaration) { var declFile = findResultCodeFile(file.unitName); if (!findUnit(declFile.fileName, declInputFiles) && !findUnit(declFile.fileName, declOtherFiles)) { dtsFiles.push({ unitName: declFile.fileName, content: declFile.code }); @@ -1510,7 +1540,8 @@ module Harness { "errortruncation", "usecasesensitivefilenames", "preserveconstenums", "includebuiltfile", "suppressimplicitanyindexerrors", "stripinternal", "separatecompilation", "inlinesourcemap", "maproot", "sourceroot", - "inlinesources", "emitdecoratormetadata"]; + "inlinesources", "emitdecoratormetadata", "packagemain", "packagename", + "packagedeclaration"]; function extractCompilerSettings(content: string): CompilerSetting[] { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 0e0b8d829183d..5e5e8bb46049a 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -186,6 +186,7 @@ module Harness.LanguageService { var script = this.getScriptInfo(fileName); return script ? script.version.toString() : undefined; } + getPackageDirectory(): string { return ""; } log(s: string): void { } trace(s: string): void { } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 39221d55b2945..a85676261f792 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -189,7 +189,8 @@ class ProjectRunner extends RunnerBase { getCurrentDirectory, getCanonicalFileName: Harness.Compiler.getCanonicalFileName, useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, - getNewLine: () => ts.sys.newLine + getNewLine: () => ts.sys.newLine, + getPackageDirectory: getCurrentDirectory }; } } diff --git a/src/services/shims.ts b/src/services/shims.ts index 27c70ddc878a4..35a10f710b7d5 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -55,6 +55,7 @@ module ts { getCurrentDirectory(): string; getDefaultLibFileName(options: string): string; getNewLine?(): string; + getPackageDirectory?(): string; getProjectVersion?(): string; } @@ -320,6 +321,14 @@ module ts { public getCurrentDirectory(): string { return this.shimHost.getCurrentDirectory(); } + + public getPackageDirectory(): string { + if (this.shimHost.getPackageDirectory) { + return this.shimHost.getPackageDirectory(); + } + + return this.getCurrentDirectory(); + } public getDefaultLibFileName(options: CompilerOptions): string { // Wrap the API changes for 1.5 release. This try/catch diff --git a/tests/baselines/reference/packageDeclarationEmit.js b/tests/baselines/reference/packageDeclarationEmit.js new file mode 100644 index 0000000000000..9471cccd46179 --- /dev/null +++ b/tests/baselines/reference/packageDeclarationEmit.js @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/packageDeclarationEmit.ts] //// + +//// [index.ts] + +export * from './ext'; + +//// [ext.ts] +export function func(): void { +} + +//// [ext.js] +function func() { +} +exports.func = func; +//// [index.js] +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +__export(require('./ext')); + + +//// [app.d.ts] +declare module "app/tests/cases/compiler/ext" { + function func(): void; +} +declare module "app/tests/cases/compiler/index" { + export * from "app/tests/cases/compiler/ext"; +} \ No newline at end of file diff --git a/tests/baselines/reference/packageDeclarationEmit.symbols b/tests/baselines/reference/packageDeclarationEmit.symbols new file mode 100644 index 0000000000000..62f3d9f1c2fc8 --- /dev/null +++ b/tests/baselines/reference/packageDeclarationEmit.symbols @@ -0,0 +1,8 @@ +=== tests/cases/compiler/index.ts === + +No type information for this code.export * from './ext'; +No type information for this code. +No type information for this code.=== tests/cases/compiler/ext.ts === +export function func(): void { +>func : Symbol(func, Decl(ext.ts, 0, 0)) +} diff --git a/tests/baselines/reference/packageDeclarationEmit.types b/tests/baselines/reference/packageDeclarationEmit.types new file mode 100644 index 0000000000000..76b096bf90978 --- /dev/null +++ b/tests/baselines/reference/packageDeclarationEmit.types @@ -0,0 +1,8 @@ +=== tests/cases/compiler/index.ts === + +No type information for this code.export * from './ext'; +No type information for this code. +No type information for this code.=== tests/cases/compiler/ext.ts === +export function func(): void { +>func : () => void +} diff --git a/tests/baselines/reference/separateCompilationUnspecifiedModule.errors.txt b/tests/baselines/reference/separateCompilationUnspecifiedModule.errors.txt index ab0fd7ffe9d5d..a89dd011f1f33 100644 --- a/tests/baselines/reference/separateCompilationUnspecifiedModule.errors.txt +++ b/tests/baselines/reference/separateCompilationUnspecifiedModule.errors.txt @@ -1,6 +1,6 @@ -error TS5047: Option 'separateCompilation' can only be used when either option'--module' is provided or option 'target' is 'ES6' or higher. +error TS5047: Option 'separateCompilation' can only be used when either option 'module' is provided or option 'target' is 'ES6' or higher. -!!! error TS5047: Option 'separateCompilation' can only be used when either option'--module' is provided or option 'target' is 'ES6' or higher. +!!! error TS5047: Option 'separateCompilation' can only be used when either option 'module' is provided or option 'target' is 'ES6' or higher. ==== tests/cases/compiler/separateCompilationUnspecifiedModule.ts (0 errors) ==== export var x; \ No newline at end of file diff --git a/tests/cases/compiler/packageDeclarationEmit.ts b/tests/cases/compiler/packageDeclarationEmit.ts new file mode 100644 index 0000000000000..84942a1105217 --- /dev/null +++ b/tests/cases/compiler/packageDeclarationEmit.ts @@ -0,0 +1,12 @@ +// @module: commonjs +// @target: es5 +// @packageName: app +// @packageMain: index.ts +// @packageDeclaration: app.d.ts + +// @filename: index.ts +export * from './ext'; + +// @filename: ext.ts +export function func(): void { +} \ No newline at end of file