From c0bfb9010a67c96fb9dd330cecc8f264e3fa66af Mon Sep 17 00:00:00 2001 From: Tomasz Nguyen Date: Sun, 7 Mar 2021 23:18:14 +0000 Subject: [PATCH] fix(babel-transformer): respect top of the file comments/pragma This means that the stryker mutator hooks are no longer just prepended to the beginning of file. Instead we also add the comments which means things like flow should behave correctly --- e2e/package-lock.json | 53 +++++++++++++++++++ e2e/package.json | 1 + e2e/test/flow-test-project/.babelrc | 16 ++++++ e2e/test/flow-test-project/package-lock.json | 5 ++ e2e/test/flow-test-project/package.json | 11 ++++ e2e/test/flow-test-project/src/square.js | 6 +++ e2e/test/flow-test-project/src/square.spec.js | 8 +++ e2e/test/flow-test-project/stryker.conf.json | 13 +++++ e2e/test/flow-test-project/verify/.babelrc | 16 ++++++ e2e/test/flow-test-project/verify/verify.ts | 19 +++++++ .../src/transformers/babel-transformer.ts | 5 ++ .../integration/transformers.it.spec.ts.snap | 6 +-- .../transformers/babel-transformer.spec.ts | 20 ++++++- 13 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 e2e/test/flow-test-project/.babelrc create mode 100644 e2e/test/flow-test-project/package-lock.json create mode 100644 e2e/test/flow-test-project/package.json create mode 100644 e2e/test/flow-test-project/src/square.js create mode 100644 e2e/test/flow-test-project/src/square.spec.js create mode 100644 e2e/test/flow-test-project/stryker.conf.json create mode 100644 e2e/test/flow-test-project/verify/.babelrc create mode 100644 e2e/test/flow-test-project/verify/verify.ts diff --git a/e2e/package-lock.json b/e2e/package-lock.json index e3ee9745af..7d6be68ac6 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -515,6 +515,23 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-flow": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.13.tgz", + "integrity": "sha512-J/RYxnlSLXZLVR7wTRsozxKT8qbsx1mNKJzXEEjQ0Kjx1ZACcyHgbanNWNCFtc36IzuWhYWPpvJFFoexoOWFmA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "dev": true + } + } + }, "@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -731,6 +748,24 @@ "@babel/helper-plugin-utils": "^7.10.1" } }, + "@babel/plugin-transform-flow-strip-types": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.13.0.tgz", + "integrity": "sha512-EXAGFMJgSX8gxWD7PZtW/P6M+z74jpx3wm/+9pn+c2dOawPpBkUX7BrfyPvo6ZpXbgRIEuwgwDb/MGlKvu2pOg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-flow": "^7.12.13" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "dev": true + } + } + }, "@babel/plugin-transform-for-of": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", @@ -1008,6 +1043,24 @@ } } }, + "@babel/preset-flow": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.12.13.tgz", + "integrity": "sha512-gcEjiwcGHa3bo9idURBp5fmJPcyFPOszPQjztXrOjUE2wWVqc6fIVJPgWPIQksaQ5XZ2HWiRsf2s1fRGVjUtVw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/plugin-transform-flow-strip-types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "dev": true + } + } + }, "@babel/runtime": { "version": "7.10.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz", diff --git a/e2e/package.json b/e2e/package.json index ad4bc39f9e..cfe46a7963 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -8,6 +8,7 @@ "@babel/plugin-proposal-class-properties": "~7.8.3", "@babel/plugin-proposal-pipeline-operator": "~7.8.3", "@babel/preset-env": "~7.8.3", + "@babel/preset-flow": "7.12.13", "@types/node": "^10.12.18", "@types/semver": "~6.2.0", "ajv": "~7.0.2", diff --git a/e2e/test/flow-test-project/.babelrc b/e2e/test/flow-test-project/.babelrc new file mode 100644 index 0000000000..75412a8780 --- /dev/null +++ b/e2e/test/flow-test-project/.babelrc @@ -0,0 +1,16 @@ +{ + "presets": [ + "@babel/preset-flow", + [ + "@babel/preset-env", + { + "targets": { + "edge": "16", + "firefox": "57", + "chrome": "62", + "safari": "11" + } + } + ] + ] +} diff --git a/e2e/test/flow-test-project/package-lock.json b/e2e/test/flow-test-project/package-lock.json new file mode 100644 index 0000000000..3e3fc7d776 --- /dev/null +++ b/e2e/test/flow-test-project/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "flow-jest-project", + "version": "0.0.0", + "lockfileVersion": 1 +} diff --git a/e2e/test/flow-test-project/package.json b/e2e/test/flow-test-project/package.json new file mode 100644 index 0000000000..4c971c297f --- /dev/null +++ b/e2e/test/flow-test-project/package.json @@ -0,0 +1,11 @@ +{ + "name": "flow-jest-project", + "version": "0.0.0", + "private": true, + "description": "A module to test a flow project, see https://flow.org/", + "scripts": { + "test:unit": "jest", + "test": "stryker run", + "posttest": "mocha --no-config --require ../../tasks/ts-node-register.js verify/*.ts" + } +} diff --git a/e2e/test/flow-test-project/src/square.js b/e2e/test/flow-test-project/src/square.js new file mode 100644 index 0000000000..c20087effa --- /dev/null +++ b/e2e/test/flow-test-project/src/square.js @@ -0,0 +1,6 @@ +// @flow +export function square(n: number): number { + const result: Array = []; + const cat = new Set(); + return n * n; +} diff --git a/e2e/test/flow-test-project/src/square.spec.js b/e2e/test/flow-test-project/src/square.spec.js new file mode 100644 index 0000000000..9549ccb346 --- /dev/null +++ b/e2e/test/flow-test-project/src/square.spec.js @@ -0,0 +1,8 @@ +import { square } from './square'; +import { expect } from 'chai'; + +describe('square', () => { + it('should provide 4 when given 2', () => { + expect(square(2)).eq(4); + }); +}); diff --git a/e2e/test/flow-test-project/stryker.conf.json b/e2e/test/flow-test-project/stryker.conf.json new file mode 100644 index 0000000000..af7e8281b0 --- /dev/null +++ b/e2e/test/flow-test-project/stryker.conf.json @@ -0,0 +1,13 @@ +{ + "$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", + "reporters": [ + "clear-text", + "event-recorder" + ], + "concurrency": 2, + "commandRunner": { + "command": "npm run test:unit" + }, + "symlinkNodeModules": false, + "fileLogLevel": "info" +} diff --git a/e2e/test/flow-test-project/verify/.babelrc b/e2e/test/flow-test-project/verify/.babelrc new file mode 100644 index 0000000000..75412a8780 --- /dev/null +++ b/e2e/test/flow-test-project/verify/.babelrc @@ -0,0 +1,16 @@ +{ + "presets": [ + "@babel/preset-flow", + [ + "@babel/preset-env", + { + "targets": { + "edge": "16", + "firefox": "57", + "chrome": "62", + "safari": "11" + } + } + ] + ] +} diff --git a/e2e/test/flow-test-project/verify/verify.ts b/e2e/test/flow-test-project/verify/verify.ts new file mode 100644 index 0000000000..985880a601 --- /dev/null +++ b/e2e/test/flow-test-project/verify/verify.ts @@ -0,0 +1,19 @@ +import { expectMetrics } from '../../../helpers'; + +describe('After running stryker on jest-react project', () => { + it('should report expected scores', async () => { + await expectMetrics({ + killed: 2, + ignored: 0, + survived: 1, + mutationScore: 66.67, + }); + /* + -----------|---------|----------|-----------|------------|----------|---------| + File | % score | # killed | # timeout | # survived | # no cov | # error | + -----------|---------|----------|-----------|------------|----------|---------| + All files | 66.67 | 2 | 0 | 1 | 0 | 0 | + square.js | 66.67 | 2 | 0 | 1 | 0 | 0 | + -----------|---------|----------|-----------|------------|----------|---------|*/ + }); +}); diff --git a/packages/instrumenter/src/transformers/babel-transformer.ts b/packages/instrumenter/src/transformers/babel-transformer.ts index e064c69674..a5d7921fff 100644 --- a/packages/instrumenter/src/transformers/babel-transformer.ts +++ b/packages/instrumenter/src/transformers/babel-transformer.ts @@ -36,6 +36,11 @@ export const transformBabel: AstTransformer = ({ ro }, }); if (mutantCollector.hasPlacedMutants(originFileName)) { + const innerComments = root.program.innerComments ?? []; + const leadingComments = root.program.body[0]?.leadingComments ?? []; + if (Array.isArray(leadingComments)) { + instrumentationBabelHeader[0].leadingComments = [...innerComments, ...leadingComments]; + } root.program.body.unshift(...instrumentationBabelHeader); } }; diff --git a/packages/instrumenter/test/integration/transformers.it.spec.ts.snap b/packages/instrumenter/test/integration/transformers.it.spec.ts.snap index ccf57806a4..cccd490ee8 100644 --- a/packages/instrumenter/test/integration/transformers.it.spec.ts.snap +++ b/packages/instrumenter/test/integration/transformers.it.spec.ts.snap @@ -1702,7 +1702,7 @@ Object { "type": "Identifier", }, "innerComments": undefined, - "leadingComments": undefined, + "leadingComments": Array [], "loc": SourceLocation { "end": Position { "column": 1, @@ -6838,7 +6838,7 @@ Object { "type": "Identifier", }, "innerComments": undefined, - "leadingComments": undefined, + "leadingComments": Array [], "loc": SourceLocation { "end": Position { "column": 1, @@ -12024,7 +12024,7 @@ Object { "type": "Identifier", }, "innerComments": undefined, - "leadingComments": undefined, + "leadingComments": Array [], "loc": SourceLocation { "end": Position { "column": 1, diff --git a/packages/instrumenter/test/unit/transformers/babel-transformer.spec.ts b/packages/instrumenter/test/unit/transformers/babel-transformer.spec.ts index 72593b98ac..85b5694c07 100644 --- a/packages/instrumenter/test/unit/transformers/babel-transformer.spec.ts +++ b/packages/instrumenter/test/unit/transformers/babel-transformer.spec.ts @@ -80,10 +80,26 @@ describe('babel-transformer', () => { expect(mutantCollectorMock.markMutantsAsPlaced).calledWith([mutant]); }); - it('should add the global stuff on top', () => { - const ast = createJSAst({ rawContent: 'foo' }); + it('should add the global stuff on top but after comments that are followed by newline', () => { + const ast = createJSAst({ rawContent: '// @flow\n// another comment\n\nconst foo="cat"' }); mutantCollectorMock.hasPlacedMutants.returns(true); transformBabel(ast, mutantCollectorMock, context); + + expect(ast.root.program.body[0].leadingComments![0].value).eq(' @flow'); + expect(ast.root.program.body[0].leadingComments![1].value).eq(' another comment'); + + for (let i = 0; i < instrumentationBabelHeader.length; i++) { + expect(ast.root.program.body[i]).eq(instrumentationBabelHeader[i]); + } + }); + it('should add the global stuff on top but after comments that are followed by a statement', () => { + const ast = createJSAst({ rawContent: '// @flow\n// another comment\nconst foo="cat"' }); + mutantCollectorMock.hasPlacedMutants.returns(true); + transformBabel(ast, mutantCollectorMock, context); + + expect(ast.root.program.body[0].leadingComments![0].value).eq(' @flow'); + expect(ast.root.program.body[0].leadingComments![1].value).eq(' another comment'); + for (let i = 0; i < instrumentationBabelHeader.length; i++) { expect(ast.root.program.body[i]).eq(instrumentationBabelHeader[i]); }