Skip to content

Commit

Permalink
fix(duplicate files): make transpile always result in unique file nam…
Browse files Browse the repository at this point in the history
…es (#1405)

Make sure that the typescript transpiler plugin always results in unique
file names. For example, given the following input:

```
foo.js
foo.ts
```

It will transpile `foo.ts` to `foo.js`, the result is now:

```
foo.js
```
  • Loading branch information
nicojs committed Feb 27, 2019
1 parent 1c71ea4 commit a3018d2
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 41 deletions.
35 changes: 19 additions & 16 deletions packages/typescript/src/TypescriptTranspiler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import flatMap = require('lodash.flatmap');
import * as ts from 'typescript';
import { Transpiler } from '@stryker-mutator/api/transpile';
import { File, StrykerOptions } from '@stryker-mutator/api/core';
Expand Down Expand Up @@ -31,7 +30,7 @@ export default class TypescriptTranspiler implements Transpiler {
if (error.length) {
return Promise.reject(new Error(error));
} else {
const resultFiles: File[] = this.transpileFiles(files);
const resultFiles = this.transpileFiles(files);
return Promise.resolve(resultFiles);
}
}
Expand All @@ -47,23 +46,27 @@ export default class TypescriptTranspiler implements Transpiler {
compilerOptions, typescriptFiles, getProjectDirectory(this.options), this.produceSourceMaps, this.getLogger);
}

private transpileFiles(files: ReadonlyArray<File>) {
private transpileFiles(files: ReadonlyArray<File>): ReadonlyArray<File> {
let isSingleOutput = false;
// Keep original order of the files using a flatmap.
return flatMap(files, file => {
if (!isHeaderFile(file.name) && this.filter.isIncluded(file.name)) {
// File is to be transpiled. Only emit if more output is expected.
if (isSingleOutput) {
return [];
} else {
const emitOutput = this.languageService.emit(file.name);
isSingleOutput = emitOutput.singleResult;
return emitOutput.outputFiles;
const fileDictionary: { [name: string]: File } = {};
files.forEach(file => fileDictionary[file.name] = file);
files.forEach(file => {
if (!isHeaderFile(file.name)) {
if (this.filter.isIncluded(file.name)) {

// File is to be transpiled. Only emit if more output is expected.
if (!isSingleOutput) {
const emitOutput = this.languageService.emit(file.name);
isSingleOutput = emitOutput.singleResult;
emitOutput.outputFiles.forEach(file => fileDictionary[file.name] = file);
}

// Remove original file
delete fileDictionary[file.name];
}
} else {
// File is not an included typescript file
return [file];
}
});

return Object.keys(fileDictionary).map(name => fileDictionary[name]);
}
}
39 changes: 14 additions & 25 deletions packages/typescript/test/unit/TypescriptTranspiler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import sinon = require('sinon');
import { testInjector } from '@stryker-mutator/test-helpers';
import { commonTokens } from '@stryker-mutator/api/plugin';

describe('TypescriptTranspiler', () => {
describe(TypescriptTranspiler.name, () => {

let languageService: sinon.SinonStubbedInstance<TranspilingLanguageService>;
let sut: TypescriptTranspiler;
Expand Down Expand Up @@ -96,35 +96,20 @@ describe('TypescriptTranspiler', () => {
expect(languageService.emit).calledWith('file1.ts');
});

it('should keep order if single output result file', async () => {
it('should remove duplicate files (issue 1318)', async () => {
// Arrange
const input = [
new File('file1.ts', 'file1'),
new File('file2.ts', 'file2'),
new File('file3.bin', Buffer.from([1, 2, 3])),
new File('file4.ts', 'file4'),
new File('file5.ts', 'file5')
];
const allOutput = new File('allOutput.js', 'single output');
const emitResult: EmitOutput = {
outputFiles: [allOutput],
singleResult: true
};
languageService.emit.returns(emitResult);
arrangeIncludedFiles([input[1], input[3]]);
const inputFile = new File('file1.ts', 'const foo: number = 42;');
const expectedOutputFile = new File('file1.js', 'const foo = 42;');
arrangeIncludedFiles([inputFile]);
const input = [inputFile, new File('file1.js', 'js content')];
languageService.emit.returns(multiResult(expectedOutputFile));

// Act
const outputFiles = await sut.transpile(input);

// Assert
expectFilesEqual(outputFiles, [
new File('file1.ts', 'file1'),
allOutput,
new File('file3.bin', Buffer.from([1, 2, 3])),
new File('file5.ts', 'file5')
]);
expect(languageService.emit).calledOnce;
expect(languageService.emit).calledWith('file2.ts');
expect(outputFiles).lengthOf(1);
expect(outputFiles[0]).eq(expectedOutputFile);
});

it('should reject errors when there are diagnostic messages', async () => {
Expand All @@ -136,7 +121,11 @@ describe('TypescriptTranspiler', () => {
});

function expectFilesEqual(actual: ReadonlyArray<File>, expected: ReadonlyArray<File>) {
expect(serialize(actual)).eq(serialize(expected));
expect(serialize(orderByName(actual))).eq(serialize(orderByName(expected)));
}

function orderByName(files: ReadonlyArray<File>) {
files.slice().sort((a, b) => a.name.localeCompare(b.name));
}

function multiResult(file: File): EmitOutput {
Expand Down

0 comments on commit a3018d2

Please sign in to comment.