From 2f6ae527d19d272d1708a400a09e0bed269cadb8 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 28 Sep 2017 11:42:58 -0700 Subject: [PATCH] fix(compiler): make sure our out path calculation is correct --- .../compiler-cli/src/transformers/program.ts | 73 +++++++++++-------- .../test/transformers/program_spec.ts | 23 ++++++ 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index 2eb6366f62..4c71c6e147 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -210,7 +210,15 @@ class AngularCompilerProgram implements Program { return emitResult; } - const srcToOutPath = this.createSrcToOutPathMapper(outSrcMapping); + + let sampleSrcFileName: string|undefined; + let sampleOutFileName: string|undefined; + if (outSrcMapping.length) { + sampleSrcFileName = outSrcMapping[0].sourceFile.fileName; + sampleOutFileName = outSrcMapping[0].outFileName; + } + const srcToOutPath = + createSrcToOutPathMapper(this.options.outDir, sampleSrcFileName, sampleOutFileName); if (emitFlags & EmitFlags.Codegen) { genFiles.forEach(gf => { if (gf.source) { @@ -287,29 +295,6 @@ class AngularCompilerProgram implements Program { return {before: beforeTs, after: afterTs}; } - private createSrcToOutPathMapper(outSrcMappings: - Array<{sourceFile: ts.SourceFile, outFileName: string}>): - (srcFileName: string) => string { - let srcToOutPath: (srcFileName: string) => string; - if (this.options.outDir) { - // TODO(tbosch): talk to TypeScript team to expose their logic for calculating the `rootDir` - // if none was specified. - if (outSrcMappings.length === 0) { - throw new Error(`Can't calculate the rootDir without at least one outSrcMapping. `); - } - const firstEntry = outSrcMappings[0]; - const entrySrcDir = path.dirname(firstEntry.sourceFile.fileName); - const entryOutDir = path.dirname(firstEntry.outFileName); - const commonSuffix = longestCommonSuffix(entrySrcDir, entryOutDir); - const rootDir = entrySrcDir.substring(0, entrySrcDir.length - commonSuffix.length); - srcToOutPath = (srcFileName) => - path.resolve(this.options.outDir, path.relative(rootDir, srcFileName)); - } else { - srcToOutPath = (srcFileName) => srcFileName; - } - return srcToOutPath; - } - private initSync() { if (this._analyzedModules) { return; @@ -567,12 +552,42 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] { return []; } -function longestCommonSuffix(a: string, b: string): string { - let len = 0; - while (a.charCodeAt(a.length - 1 - len) === b.charCodeAt(b.length - 1 - len)) { - len++; +/** + * Returns a function that can adjust a path from source path to out path, + * based on an existing mapping from source to out path. + * + * TODO(tbosch): talk to the TypeScript team to expose their logic for calculating the `rootDir` + * if none was specified. + * + * @param outDir + * @param outSrcMappings + */ +export function createSrcToOutPathMapper( + outDir: string | undefined, sampleSrcFileName: string | undefined, + sampleOutFileName: string | undefined): (srcFileName: string) => string { + let srcToOutPath: (srcFileName: string) => string; + if (outDir) { + if (sampleSrcFileName == null || sampleOutFileName == null) { + throw new Error(`Can't calculate the rootDir without a sample srcFileName / outFileName. `); + } + const srcFileDir = path.dirname(sampleSrcFileName); + const outFileDir = path.dirname(sampleOutFileName); + if (srcFileDir === outFileDir) { + return (srcFileName) => srcFileName; + } + const srcDirParts = srcFileDir.split(path.sep); + const outDirParts = outFileDir.split(path.sep); + // calculate the common suffix + let i = 0; + while (i < Math.min(srcDirParts.length, outDirParts.length) && + srcDirParts[srcDirParts.length - 1 - i] === outDirParts[outDirParts.length - 1 - i]) + i++; + const rootDir = srcDirParts.slice(0, srcDirParts.length - i).join(path.sep); + srcToOutPath = (srcFileName) => path.resolve(outDir, path.relative(rootDir, srcFileName)); + } else { + srcToOutPath = (srcFileName) => srcFileName; } - return a.substring(a.length - len); + return srcToOutPath; } export function i18nExtract( diff --git a/packages/compiler-cli/test/transformers/program_spec.ts b/packages/compiler-cli/test/transformers/program_spec.ts index 679bd6544d..dd9216cc28 100644 --- a/packages/compiler-cli/test/transformers/program_spec.ts +++ b/packages/compiler-cli/test/transformers/program_spec.ts @@ -12,6 +12,7 @@ import * as path from 'path'; import * as ts from 'typescript'; import {CompilerHost} from '../../src/transformers/api'; +import {createSrcToOutPathMapper} from '../../src/transformers/program'; import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from '../../src/transformers/util'; import {TestSupport, expectNoDiagnosticsInProgram, setup} from '../test_support'; @@ -361,4 +362,26 @@ describe('ng program', () => { testSupport.shouldNotExist('build/node_modules/lib/index.ngfactory.d.ts'); testSupport.shouldNotExist('build/node_modules/lib/index.ngsummary.json'); }); + + describe('createSrcToOutPathMapper', () => { + it('should return identity mapping if no outDir is present', () => { + const mapper = createSrcToOutPathMapper(undefined, undefined, undefined); + expect(mapper('/tmp/b/y.js')).toBe('/tmp/b/y.js'); + }); + + it('should return identity mapping if first src and out fileName have same dir', () => { + const mapper = createSrcToOutPathMapper('/tmp', '/tmp/a/x.ts', '/tmp/a/x.js'); + expect(mapper('/tmp/b/y.js')).toBe('/tmp/b/y.js'); + }); + + it('should adjust the filename if the outDir is inside of the rootDir', () => { + const mapper = createSrcToOutPathMapper('/tmp/out', '/tmp/a/x.ts', '/tmp/out/a/x.js'); + expect(mapper('/tmp/b/y.js')).toBe('/tmp/out/b/y.js'); + }); + + it('should adjust the filename if the outDir is outside of the rootDir', () => { + const mapper = createSrcToOutPathMapper('/out', '/tmp/a/x.ts', '/a/x.js'); + expect(mapper('/tmp/b/y.js')).toBe('/out/b/y.js'); + }); + }); });