diff --git a/packages/codemod-utils/src/transform/js.ts b/packages/codemod-utils/src/transform/js.ts index 214f6658..e4552499 100644 --- a/packages/codemod-utils/src/transform/js.ts +++ b/packages/codemod-utils/src/transform/js.ts @@ -57,6 +57,11 @@ export function transformJsFile( options: AstTransformOptions & Required>, ): AstTransformResult { const { applyDangerousEdits, fs, ...parserOptions } = options; + // Attempt to determine input file line endings, defaulting to the operating system default + const crlfLineEndings = source.includes('\r\n'); + const lfLineEndings = !crlfLineEndings && source.includes('\n'); + const lineTerminator = crlfLineEndings ? '\r\n' : lfLineEndings ? '\n' : undefined; + // Parse the source AST const ast = parse(source, { parser: { sourceFilename: parserOptions.sourceFilename, @@ -70,6 +75,7 @@ export function transformJsFile( }, }, }) as ReturnType; + // Transform the AST const uniqueErrors = new Map(); const transformContext: AstTransformContext = { filename: parserOptions.sourceFilename, @@ -83,8 +89,14 @@ export function transformJsFile( }, }; const transformedAst = transformAst(ast, transforms, transformContext, { source }); + // If there were no modifications to the AST, return a null result if (!transformedAst && uniqueErrors.size === 0) return { source: null, errors: [] }; - const transformedSource = transformedAst ? print(transformedAst).code : null; + // Print the transformed AST + const transformedSource = transformedAst + ? print(transformedAst, { + lineTerminator, + }).code + : null; return { source: transformedSource === source ? null : transformedSource, errors: Array.from(uniqueErrors.values()), diff --git a/packages/codemods/src/versions/31.0.0/codemod.test.ts b/packages/codemods/src/versions/31.0.0/codemod.test.ts new file mode 100644 index 00000000..5a9e83fa --- /dev/null +++ b/packages/codemods/src/versions/31.0.0/codemod.test.ts @@ -0,0 +1,185 @@ +import { type CodemodFsUtils } from '@ag-grid-devtools/types'; +import { fs as memfs } from 'memfs'; +import { describe, expect, test } from 'vitest'; + +import codemod from './codemod'; + +describe('Retains line endings', () => { + describe('CRLF', () => { + describe('JavaScript source files', () => { + test('No modifications', () => { + const input = [ + `import { Foo } from 'unrelated-package';`, + `new Foo(document.body, { bar: true });`, + ].join('\r\n'); + const actual = codemod( + { path: './input.js', source: input }, + { + applyDangerousEdits: true, + fs: createFsHelpers(memfs), + }, + ); + expect(actual).toEqual({ source: null, errors: [] }); + }); + + test('Modifications', () => { + const input = [ + `import { Grid } from 'ag-grid-community';`, + `const options = { foo: true };`, + `new Grid(document.body, options);`, + ].join('\r\n'); + const expected = [ + `import { createGrid } from 'ag-grid-community';`, + `const options = { foo: true };`, + `const optionsApi = createGrid(document.body, options);`, + ].join('\r\n'); + const actual = codemod( + { path: './input.js', source: input }, + { + applyDangerousEdits: true, + fs: createFsHelpers(memfs), + }, + ); + expect(actual).toEqual({ source: expected, errors: [] }); + }); + }); + + describe('Vue source files', () => { + test('No modifications', () => { + const input = [ + ``, + ``, + `