Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added single-file package declaration using multiple ambient modules
  • Loading branch information
rbuckton committed May 7, 2015
commit 094afb2cd98fa9ead82735174b99f15fb796e843
18 changes: 18 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ module ts {
type: "boolean",
description: Diagnostics.Generates_corresponding_d_ts_file,
},
{
name: "packageMain",
type: "string",
isFilePath: true,
experimental: true
},
{
name: "packageName",
type: "string",
isFilePath: true,
experimental: true
},
{
name: "packageDeclaration",
type: "string",
isFilePath: true,
experimental: true
},
{
name: "diagnostics",
type: "boolean",
Expand Down
155 changes: 126 additions & 29 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ module ts {
let compilerOptions = host.getCompilerOptions();
let languageVersion = compilerOptions.target || ScriptTarget.ES3;

let isPackage = (compilerOptions.packageMain && compilerOptions.packageName && compilerOptions.packageDeclaration) != undefined;
let packageMainFile: string;
let write: (s: string) => void;
let writeLine: () => void;
let increaseIndent: () => void;
Expand All @@ -63,6 +65,10 @@ module ts {
// 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 (isPackage) {
packageMainFile = host.getCanonicalFileName(compilerOptions.packageMain);
}

if (root) {
// Emitting just a single file, so emit references in this file only
Expand All @@ -83,9 +89,9 @@ module ts {
}
});
}

emitSourceFile(root);

// create asynchronous output for the importDeclarations
if (moduleElementDeclarationEmitInfo.length) {
let oldWriter = writer;
Expand All @@ -104,11 +110,11 @@ module ts {
else {
// Emit references corresponding to this file
let emittedReferencedFiles: SourceFile[] = [];
forEach(host.getSourceFiles(), sourceFile => {
if (!isExternalModuleOrDeclarationFile(sourceFile)) {
for (let sourceFile of sortSourceFiles(host.getSourceFiles())) {
if (!isExternalModuleOrDeclarationFile(sourceFile) || (isPackage && isExternalModule(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
Expand All @@ -117,22 +123,52 @@ module ts {

writeReferencePath(referencedFile);
emittedReferencedFiles.push(referencedFile);
}
});
}
}
}

writeLine();
emitSourceFile(sourceFile);
}
});
}
}
}

return {
reportedDeclarationError,
moduleElementDeclarationEmitInfo,
synchronousDeclarationOutput: writer.getText(),
referencePathsOutput,
}

function sortSourceFiles(sourceFiles: SourceFile[]) {
if (isPackage) {
let indices = new Array<number>(sourceFiles.length);
for (let i = 0; i < sourceFiles.length; ++i) indices[i] = i;
indices.sort((left, right) => {
let leftFile = sourceFiles[left];
if (leftFile.fileName === packageMainFile) {
return -1;
}

let rightFile = sourceFiles[right];
if (rightFile.fileName === packageMainFile) {
return +1;
}

return left - right;
});

let sorted = new Array<SourceFile>(sourceFiles.length);
for (let i = 0; i < sourceFiles.length; ++i) {
sorted[i] = sourceFiles[indices[i]];
}

return sorted;
}

return sourceFiles;
}

function hasInternalAnnotation(range: CommentRange) {
let text = currentSourceFile.text;
let comment = text.substring(range.pos, range.end);
Expand Down Expand Up @@ -439,7 +475,29 @@ module ts {
function emitSourceFile(node: SourceFile) {
currentSourceFile = node;
enclosingDeclaration = node;

if (isPackage) {
// compute file name relative to main
let packageQualifiedModuleName: string;
if (packageMainFile === node.fileName) {
packageQualifiedModuleName = compilerOptions.packageName;
}
else {
let sourcePath = removeFileExtension(node.fileName);
packageQualifiedModuleName = getPackageQualifiedPath(host, sourcePath, ".");
}
write(`declare module "${packageQualifiedModuleName}" {`);
increaseIndent();
writeLine();
}

emitLines(node.statements);

if (isPackage) {
decreaseIndent();
writeLine();
write("}");
}
}

// Return a temp variable name to be used in `export default` statements.
Expand Down Expand Up @@ -563,7 +621,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 && !isPackage) {
// If the node is exported
if (node.flags & NodeFlags.Export) {
write("export ");
Expand Down Expand Up @@ -686,7 +744,7 @@ module ts {
// write each of these declarations asynchronously
writeAsynchronousModuleElements(nodes);
}

function emitExportDeclaration(node: ExportDeclaration) {
emitJsDocComments(node);
write("export ");
Expand All @@ -700,7 +758,21 @@ module ts {
}
if (node.moduleSpecifier) {
write(" from ");
writeTextOfNode(currentSourceFile, node.moduleSpecifier);
if (isPackage) {
let moduleNameText = (<LiteralExpression>node.moduleSpecifier).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(`"${packageQualifiedPath}"`);
}
else {
writeTextOfNode(currentSourceFile, node.moduleSpecifier);
}
}
else {
writeTextOfNode(currentSourceFile, node.moduleSpecifier);
}
}
write(";");
writer.writeLine();
Expand Down Expand Up @@ -1560,7 +1632,46 @@ module ts {
referencePathsOutput += "/// <reference path=\"" + declFileName + "\" />" + newLine;
}
}


function getPackageQualifiedPath(host: EmitHost, moduleName: string, basePath: string) {
let compilerOptions = host.getCompilerOptions();
let modulePath = normalizePath(combinePaths(basePath, moduleName));
let packageRelativePath = getRelativePathToDirectoryOrUrl(
getDirectoryPath(host.getCanonicalFileName(compilerOptions.packageMain)),
modulePath,
host.getCurrentDirectory(),
host.getCanonicalFileName,
false);
return `package://${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);
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);
Expand All @@ -1569,22 +1680,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);
}
}
}
6 changes: 5 additions & 1 deletion src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_packageName_packageMain_and_packageDeclaration_must_all_be_specified_with_option_0: { code: 5052, category: DiagnosticCategory.Error, key: "Options 'packageName', 'packageMain', and 'packageDeclaration' must all be specified with option '{0}'." },
Option_declaration_must_be_specified_with_options_packageName_packageMain_and_packageDeclaration: { code: 5053, category: DiagnosticCategory.Error, key: "Option 'declaration' must be specified with options 'packageName', 'packageMain', and 'packageDeclaration'." },
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'." },
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." },
Expand Down
18 changes: 17 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand All @@ -1821,6 +1821,22 @@
"category": "Error",
"code": 5051
},
"Options 'packageName', 'packageMain', and 'packageDeclaration' must all be specified with option '{0}'.": {
"category": "Error",
"code": 5052
},
"Option 'declaration' must be specified with options 'packageName', 'packageMain', and 'packageDeclaration'.": {
"category": "Error",
"code": 5053
},
"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
},

"Concatenate and emit output to single file.": {
"category": "Message",
Expand Down
13 changes: 12 additions & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
if (compilerOptions.out) {
emitFile(compilerOptions.out);
}

if (compilerOptions.packageMain && compilerOptions.packageName && compilerOptions.packageDeclaration) {
writePackageDeclarationFile(compilerOptions.packageDeclaration, host, resolver, diagnostics);
}
}
else {
// targetSourceFile is specified (e.g calling emitter from language service or calling getSemanticDiagnostic from language service)
Expand Down Expand Up @@ -6047,7 +6051,14 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
emitJavaScript(jsFilePath, sourceFile);

if (compilerOptions.declaration) {
writeDeclarationFile(jsFilePath, sourceFile, host, resolver, diagnostics);
if (compilerOptions.packageMain && compilerOptions.packageName && compilerOptions.packageDeclaration) {
if (sourceFile.fileName === host.getCanonicalFileName(compilerOptions.packageMain)) {
writeDeclarationFile(jsFilePath, sourceFile, host, resolver, diagnostics);
}
}
else {
writeDeclarationFile(jsFilePath, sourceFile, host, resolver, diagnostics);
}
}
}
}
Expand Down
Loading