Skip to content

Commit

Permalink
Attempt to retain input file line endings in codemod
Browse files Browse the repository at this point in the history
  • Loading branch information
timkendrick committed Dec 13, 2023
1 parent dc23390 commit a7e1439
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 1 deletion.
14 changes: 13 additions & 1 deletion packages/codemod-utils/src/transform/js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export function transformJsFile(
options: AstTransformOptions & Required<Pick<ParserOptions, 'sourceType'>>,
): 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,
Expand All @@ -70,6 +75,7 @@ export function transformJsFile(
},
},
}) as ReturnType<typeof parseAst>;
// Transform the AST
const uniqueErrors = new Map<string, SyntaxError>();
const transformContext: AstTransformContext<AstCliContext> = {
filename: parserOptions.sourceFilename,
Expand All @@ -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()),
Expand Down
185 changes: 185 additions & 0 deletions packages/codemods/src/versions/31.0.0/codemod.test.ts
Original file line number Diff line number Diff line change
@@ -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 = [
`<script>`,
`export default {`,
` data() {`,
` return {`,
` greeting: 'Hello World!'`,
` }`,
` }`,
`}`,
`</script>`,
``,
`<template>`,
` <p class="greeting">{{ greeting }}</p>`,
`<template>`,
``,
`<style>`,
`.greeting {`,
` color: red;`,
` font-weight: bold;`,
`}`,
`</style>`,
].join('\r\n');
const actual = codemod(
{ path: './input.vue', source: input },
{
applyDangerousEdits: true,
fs: createFsHelpers(memfs),
},
);
expect(actual).toEqual({ source: null, errors: [] });
});

test('Modifications', () => {
const input = [
`<script>`,
`import { AgGridVue } from '@ag-grid-community/vue';`,
``,
`export default {`,
` components: {`,
` 'ag-grid-vue': AgGridVue,`,
` },`,
` data() {`,
` return {`,
` columnDefs: [],`,
` rowData: [],`,
` advancedFilterModel: {`,
` filterType: 'join',`,
` type: 'AND',`,
` conditions: [],`,
` },`,
` };`,
` },`,
`};`,
`</script>`,
``,
`<template>`,
` <div>`,
` <ag-grid-vue`,
` :columnDefs="columnDefs"`,
` :rowData="rowData"`,
` :advancedFilterModel="advancedFilterModel"`,
` ></ag-grid-vue>`,
` </div>`,
`</template>`,
].join('\r\n');
const expected = [
`<script>`,
`import { AgGridVue } from '@ag-grid-community/vue';`,
``,
`export default {`,
` components: {`,
` 'ag-grid-vue': AgGridVue,`,
` },`,
` data() {`,
` return {`,
` columnDefs: [],`,
` rowData: [],`,
` advancedFilterModel: {`,
` filter: {`,
` advancedFilterModel: {`,
` filterType: 'join',`,
` type: 'AND',`,
` conditions: []`,
` }`,
` }`,
` },`,
` };`,
` },`,
`};`,
`</script>`,
``,
`<template>`,
` <div>`,
` <ag-grid-vue`,
` :columnDefs="columnDefs"`,
` :rowData="rowData"`,
` :initialState="advancedFilterModel"`,
` ></ag-grid-vue>`,
` </div>`,
`</template>`,
].join('\r\n');
const actual = codemod(
{ path: './input.vue', source: input },
{
applyDangerousEdits: true,
fs: createFsHelpers(memfs),
},
);
expect(actual).toEqual({
source: expected,
errors: [
new SyntaxError(
'Vue components are not yet fully supported via codemods and may require manual fixes',
),
],
});
});
});
});
});

function createFsHelpers(fs: typeof memfs): CodemodFsUtils {
return {
readFileSync,
writeFileSync,
};

function readFileSync(filename: string, encoding: 'utf-8'): string;
function readFileSync(filename: string, encoding: BufferEncoding): string | Buffer;
function readFileSync(filename: string, encoding: BufferEncoding): string | Buffer {
return fs.readFileSync(filename, encoding);
}

function writeFileSync(filename: string, data: string | Buffer): void {
return fs.writeFileSync(filename, data);
}
}

0 comments on commit a7e1439

Please sign in to comment.