diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 04cc9a2e426..de7f3e45346 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14867,7 +14867,8 @@ namespace ts { collectLinkedAliases, getReferencedValueDeclaration, getTypeReferenceSerializationKind, - isOptionalParameter + isOptionalParameter, + getSymbolAtLocation }; } diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index fb1ccf5714a..82983dab394 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -729,17 +729,30 @@ namespace ts { } write(" from "); } - let match: RegExpMatchArray; - if ((!root) && node.moduleSpecifier.kind === SyntaxKind.StringLiteral && (match = getTextOfNode(node.moduleSpecifier).match(/('|")(\.\/|\.\.\/)/))) { - write(makeModulePathSemiAbsolute(host, currentSourceFile, getTextOfNode(node.moduleSpecifier))); - } - else { - writeTextOfNode(currentSourceFile, node.moduleSpecifier); - } + emitExternalModuleSpecifier(node.moduleSpecifier); write(";"); writer.writeLine(); } + function emitExternalModuleSpecifier(moduleSpecifier: Expression) { + Debug.assert(moduleSpecifier.kind === SyntaxKind.StringLiteral); + + if ((!root) && (compilerOptions.out || compilerOptions.outFile)) { + let moduleSymbol = resolver.getSymbolAtLocation(moduleSpecifier); + if (moduleSymbol && moduleSymbol.valueDeclaration && + moduleSymbol.valueDeclaration.kind === SyntaxKind.SourceFile && + !isDeclarationFile(moduleSymbol.valueDeclaration)) { + let nonRelativeModuleName = getExternalModuleNameFromPath(host, (moduleSymbol.valueDeclaration as SourceFile).fileName); + write("\""); + write(nonRelativeModuleName); + write("\""); + return; + } + } + + writeTextOfNode(currentSourceFile, moduleSpecifier); + } + function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { if (node.propertyName) { writeTextOfNode(currentSourceFile, node.propertyName); @@ -771,13 +784,7 @@ namespace ts { } if (node.moduleSpecifier) { write(" from "); - let match: RegExpMatchArray; - if ((!root) && node.moduleSpecifier.kind === SyntaxKind.StringLiteral && (match = getTextOfNode(node.moduleSpecifier).match(/('|")(\.\/|\.\.\/)/))) { - write(makeModulePathSemiAbsolute(host, currentSourceFile, getTextOfNode(node.moduleSpecifier))); - } - else { - writeTextOfNode(currentSourceFile, node.moduleSpecifier); - } + emitExternalModuleSpecifier(node.moduleSpecifier); } write(";"); writer.writeLine(); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index d62cec73bd9..278bf026603 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -497,7 +497,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitConcatenatedModule(sourceFile: SourceFile): void { currentSourceFile = sourceFile; exportFunctionForFile = undefined; - let canonicalName = resolveToSemiabsolutePath(host, sourceFile.fileName); + let canonicalName = getExternalModuleNameFromPath(host, sourceFile.fileName); sourceFile.moduleName = sourceFile.moduleName || canonicalName; emit(sourceFile); } @@ -6766,7 +6766,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } if (resolvePath) { - text = makeModulePathSemiAbsolute(host, currentSourceFile, text); + text = `"${lookupSpecifierName(externalImports[i])}"`; } write(text); } @@ -6782,6 +6782,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write("});"); } + function lookupSpecifierName(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration): string { + let specifier: Node; + if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) { + specifier = (declaration as ImportEqualsDeclaration).moduleReference; + } + else { + specifier = (declaration as ImportDeclaration|ExportDeclaration).moduleSpecifier; + } + let moduleSymbol = resolver.getSymbolAtLocation(specifier); + return getExternalModuleNameFromPath(host, (moduleSymbol.valueDeclaration as SourceFile).fileName); + } + interface AMDDependencyNames { aliasedModuleNames: string[]; unaliasedModuleNames: string[]; @@ -6813,7 +6825,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi let externalModuleName = getExternalModuleNameText(importNode); if (resolvePath) { - externalModuleName = makeModulePathSemiAbsolute(host, currentSourceFile, externalModuleName); + externalModuleName = `"${lookupSpecifierName(importNode)}"`; } // Find the name of the module alias, if there is one diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bf4acc92675..7d4a116eebc 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -372,29 +372,6 @@ namespace ts { } } - // there has to be common source directory if user specified --outdir || --sourceRoot - // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted - if (options.outDir || // there is --outDir specified - options.sourceRoot || // there is --sourceRoot specified - options.mapRoot) { // there is --mapRoot specified - - if (options.rootDir && checkSourceFilesBelongToPath(files, options.rootDir)) { - // If a rootDir is specified and is valid use it as the commonSourceDirectory - commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, host.getCurrentDirectory()); - } - else { - // Compute the commonSourceDirectory from the input files - commonSourceDirectory = computeCommonSourceDirectory(files); - } - - if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { - // Make sure directory path ends with directory separator so this string can directly - // used to replace with "" to get the relative path of the source file and the relative path doesn't - // start with / making it rooted path - commonSourceDirectory += directorySeparator; - } - } - verifyCompilerOptions(); // unconditionally set oldProgram to undefined to prevent it from being captured in closure @@ -540,12 +517,6 @@ namespace ts { getSourceFiles: program.getSourceFiles, writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError) => host.writeFile(fileName, data, writeByteOrderMark, onError)), - resolveModuleName: (name: string, containingFile?: string) => { - let resolvedModule = resolveModuleNamesWorker([name], containingFile || "dummy.ts")[0]; - if (!resolvedModule) - return; - return resolvedModule.resolvedFileName; - }, }; } @@ -1063,6 +1034,29 @@ namespace ts { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile")); } + // there has to be common source directory if user specified --outdir || --sourceRoot + // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted + if (options.outDir || // there is --outDir specified + options.sourceRoot || // there is --sourceRoot specified + options.mapRoot) { // there is --mapRoot specified + + if (options.rootDir && checkSourceFilesBelongToPath(files, options.rootDir)) { + // If a rootDir is specified and is valid use it as the commonSourceDirectory + commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, host.getCurrentDirectory()); + } + else { + // Compute the commonSourceDirectory from the input files + commonSourceDirectory = computeCommonSourceDirectory(files); + } + + if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { + // Make sure directory path ends with directory separator so this string can directly + // used to replace with "" to get the relative path of the source file and the relative path doesn't + // start with / making it rooted path + commonSourceDirectory += directorySeparator; + } + } + if (options.noEmit) { if (options.out) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmit", "out")); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2818ef04a48..fb6e237dd39 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1611,6 +1611,7 @@ namespace ts { getReferencedValueDeclaration(reference: Identifier): Declaration; getTypeReferenceSerializationKind(typeName: EntityName): TypeReferenceSerializationKind; isOptionalParameter(node: ParameterDeclaration): boolean; + getSymbolAtLocation(node: Node): Symbol; } export const enum SymbolFlags { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d95d53f97ef..24916c2e106 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -40,7 +40,6 @@ namespace ts { getNewLine(): string; writeFile: WriteFileCallback; - resolveModuleName(path: string, containingFile?: string): string; } // Pool writers to avoid needing to allocate them for every symbol we write. @@ -1608,7 +1607,13 @@ namespace ts { } } - let baseCharsMap: Map = { + // This consists of the first 19 unprintable ASCII characters, canonical escapes, lineSeparator, + // paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in + // the language service. These characters should be escaped when printing, and if any characters are added, + // the map below must be updated. Note that this regexp *does not* include the 'delete' character. + // There is no reason for this other than that JSON.stringify does not handle it either. + let escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; + let escapedCharsMap: Map = { "\0": "\\0", "\t": "\\t", "\v": "\\v", @@ -1617,25 +1622,12 @@ namespace ts { "\r": "\\r", "\n": "\\n", "\\": "\\\\", + "\"": "\\\"", "\u2028": "\\u2028", // lineSeparator "\u2029": "\\u2029", // paragraphSeparator "\u0085": "\\u0085" // nextLine }; - // This consists of the first 19 unprintable ASCII characters, canonical escapes, lineSeparator, - // paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in - // the language service. These characters should be escaped when printing, and if any characters are added, - // the map below must be updated. Note that this regexp *does not* include the 'delete' character. - // There is no reason for this other than that JSON.stringify does not handle it either. - let escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; - let escapedCharsMap: Map = extend(baseCharsMap, { - "\"": "\\\"" - }); - - let singleQuoteEscapedCharsRegExp = /[\\\'\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; - let singleQuoteEscapedCharsMap: Map = extend(baseCharsMap, { - "\'": "\\\'" - }); /** * Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2), @@ -1643,46 +1635,15 @@ namespace ts { * Note that this doesn't actually wrap the input in double quotes. */ export function escapeString(s: string): string { - return escapeStringByQuote(s, QuotationMark.Double); - } - - export const enum QuotationMark { - Double, - Single - } - - export var QuotationMarkLookup: Map = { - [QuotationMark.Double]: "\"", - [QuotationMark.Single]: "'", - "\"": QuotationMark.Double.toString(), - "'": QuotationMark.Single.toString(), - }; - - /** - * A generalization of the escape function described by escapeString configuable to handle - * either single quotes or double quotes - */ - export function escapeStringByQuote(s: string, quotationMark: QuotationMark): string { - let regex = quotationMark === QuotationMark.Single ? singleQuoteEscapedCharsRegExp : escapedCharsRegExp; - let replacementMap = quotationMark === QuotationMark.Single ? singleQuoteEscapedCharsMap : escapedCharsMap; - - s = regex.test(s) ? s.replace(regex, getReplacement) : s; + s = escapedCharsRegExp.test(s) ? s.replace(escapedCharsRegExp, getReplacement) : s; return s; function getReplacement(c: string) { - return replacementMap[c] || get16BitUnicodeEscapeSequence(c.charCodeAt(0)); + return escapedCharsMap[c] || get16BitUnicodeEscapeSequence(c.charCodeAt(0)); } } - /** - * Quotes a given string akin to the abstract Quote operation from ECMA-262 (24.3.2.2) - * with the specified quotation mark - */ - export function quoteString(s: string, quotemark: QuotationMark): string { - return QuotationMarkLookup[quotemark] + escapeStringByQuote(s, quotemark) + QuotationMarkLookup[quotemark]; - } - export function isIntrinsicJsxName(name: string) { let ch = name.substr(0, 1); return ch.toLowerCase() === ch; @@ -1796,23 +1757,12 @@ namespace ts { }; } - export function makeModulePathSemiAbsolute(host: EmitHost, currentSourceFile: SourceFile, externalModuleName: string): string { - let quotationMark = externalModuleName.charAt(0); - let unquotedModuleName = externalModuleName.substring(1, externalModuleName.length - 1); - let resolvedFileName = host.resolveModuleName(unquotedModuleName, currentSourceFile.fileName); - if (resolvedFileName) { - let semiAbsoluteName = resolveToSemiabsolutePath(host, resolvedFileName); - externalModuleName = quoteString(semiAbsoluteName, parseInt(QuotationMarkLookup[quotationMark])); - } - return externalModuleName; - } - /** * Resolves a local path to a path which is absolute to the base of the emit */ - export function resolveToSemiabsolutePath(host: EmitHost, path: string): string { + export function getExternalModuleNameFromPath(host: EmitHost, fileName: string): string { let dir = host.getCurrentDirectory(); - let relativePath = getRelativePathToDirectoryOrUrl(dir, path, dir, f => host.getCanonicalFileName(f), /*isAbsolutePathAnUrl*/ false); + let relativePath = getRelativePathToDirectoryOrUrl(dir, fileName, dir, f => host.getCanonicalFileName(f), /*isAbsolutePathAnUrl*/ false); return removeFileExtension(relativePath); } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 862e446352d..18a8bc94c7b 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -366,7 +366,7 @@ class ProjectRunner extends RunnerBase { return resolutionInfo; } - it(name + ": " + moduleNameToString(moduleKind) , () => { + it(name + ": " + moduleNameToString(moduleKind), () => { // Compile using node compilerResult = batchCompilerProjectTestCase(moduleKind); });