Skip to content

Commit 56ceeb2

Browse files
mbtoolsbjohansebas
andauthored
fix: conversion of LF to CRLF (#56)
* fix: conversion of LF to CRLF * fix lint error Signed-off-by: Sebastian Beltran <[email protected]> * Add test for EOL detection * lint * fix lint Signed-off-by: Sebastian Beltran <[email protected]> --------- Signed-off-by: Sebastian Beltran <[email protected]> Co-authored-by: Sebastian Beltran <[email protected]>
1 parent dae760b commit 56ceeb2

File tree

6 files changed

+93
-4
lines changed

6 files changed

+93
-4
lines changed

transforms/magic-redirect.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
memberExpression,
1111
} from 'jscodeshift'
1212
import { getParsedFile } from '../utils/parse'
13+
import { getOptions } from '../utils/recastOptions'
1314
import { recursiveParent } from '../utils/recursiveParent'
1415

1516
const unifiedMagicString = (path: ASTPath<CallExpression>, projectRequestName: string) => {
@@ -51,5 +52,5 @@ export default function transformer(file: FileInfo, _api: API) {
5152
})
5253
.map((path) => unifiedMagicString(path, recursiveParent(path.parentPath) || 'req'))
5354

54-
return parsedFile.toSource()
55+
return parsedFile.toSource(getOptions(file.source))
5556
}

transforms/pluralized-methods.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { API, FileInfo } from 'jscodeshift'
22
import { Identifier, identifier } from 'jscodeshift'
33
import { getParsedFile } from '../utils/parse'
4+
import { getOptions } from '../utils/recastOptions'
45

56
export default function transformer(file: FileInfo, _api: API): string {
67
const parsedFile = getParsedFile(file)
@@ -17,5 +18,5 @@ export default function transformer(file: FileInfo, _api: API): string {
1718
.replaceWith(() => identifier(plural))
1819
}
1920

20-
return parsedFile.toSource()
21+
return parsedFile.toSource(getOptions(file.source))
2122
}

transforms/req-param.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { API, FileInfo } from 'jscodeshift'
22
import { CallExpression, identifier, memberExpression, withParser } from 'jscodeshift'
3+
import { getOptions } from '../utils/recastOptions'
34
import { recursiveParent } from '../utils/recursiveParent'
45

56
export default function transformer(file: FileInfo, _api: API): string {
@@ -40,5 +41,5 @@ export default function transformer(file: FileInfo, _api: API): string {
4041

4142
return path
4243
})
43-
.toSource()
44+
.toSource(getOptions(file.source))
4445
}

transforms/v4-deprecated-signatures.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { API, ASTPath, FileInfo } from 'jscodeshift'
22
import { CallExpression, callExpression, identifier, memberExpression, withParser } from 'jscodeshift'
3+
import { getOptions } from '../utils/recastOptions'
34
import { recursiveParent } from '../utils/recursiveParent'
45

56
const separateStatusAndBody = (path: ASTPath<CallExpression>, calleePropertyName: string) => {
@@ -153,5 +154,5 @@ export default function transformer(file: FileInfo, _api: API): string {
153154
return path
154155
})
155156

156-
return parsedFile.toSource()
157+
return parsedFile.toSource(getOptions(file.source))
157158
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { getOptions } from '../recastOptions'
2+
3+
describe('recastOptions', () => {
4+
describe('getOptions', () => {
5+
it('should return Unix line terminator for code with only LF', () => {
6+
const code = 'const a = 1\nconst b = 2\n'
7+
const result = getOptions(code)
8+
9+
expect(result).toEqual({ lineTerminator: '\n' })
10+
})
11+
12+
it('should return Windows line terminator for code with only CRLF', () => {
13+
const code = 'const a = 1\r\nconst b = 2\r\n'
14+
const result = getOptions(code)
15+
16+
expect(result).toEqual({ lineTerminator: '\r\n' })
17+
})
18+
19+
it('should return Unix line terminator for code with mixed line terminators', () => {
20+
const code = 'const a = 1\r\nconst b = 2\nconst c = 3\r\n'
21+
const result = getOptions(code)
22+
23+
expect(result).toEqual({ lineTerminator: '\n' })
24+
})
25+
26+
it('should return Unix line terminator for code with no line terminators', () => {
27+
const code = 'const a = 1'
28+
const result = getOptions(code)
29+
30+
expect(result).toEqual({ lineTerminator: '\n' })
31+
})
32+
33+
it('should return Unix line terminator for empty string', () => {
34+
const code = ''
35+
const result = getOptions(code)
36+
37+
expect(result).toEqual({ lineTerminator: '\n' })
38+
})
39+
40+
it('should handle code with LF at beginning after CR', () => {
41+
const code = '\r\nconst a = 1\nconst b = 2'
42+
const result = getOptions(code)
43+
44+
expect(result).toEqual({ lineTerminator: '\n' })
45+
})
46+
47+
it('should handle single CRLF without other line breaks', () => {
48+
const code = 'const a = 1\r\nconst b = 2'
49+
const result = getOptions(code)
50+
51+
expect(result).toEqual({ lineTerminator: '\r\n' })
52+
})
53+
54+
it('should handle multiple CRLF without LF', () => {
55+
const code = 'line1\r\nline2\r\nline3\r\n'
56+
const result = getOptions(code)
57+
58+
expect(result).toEqual({ lineTerminator: '\r\n' })
59+
})
60+
61+
it('should detect LF even when preceded by CR in different context', () => {
62+
const code = 'const str = "\\r"\nconst a = 1\r\n'
63+
const result = getOptions(code)
64+
65+
expect(result).toEqual({ lineTerminator: '\n' })
66+
})
67+
})
68+
})

utils/recastOptions.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* By default, jscodeshift(recast) uses the line terminator of the OS the code runs on.
3+
* This is often not desired, so we instead try to detect it from the input.
4+
* If there is at least one Windows-style linebreak (CRLF) in the input and
5+
* no Unix-style linebreak (LF), use that. In all other cases, use Unix-style (LF).
6+
* @return '\n' or '\r\n'
7+
*/
8+
export function getOptions(code: string) {
9+
return { lineTerminator: detectLineTerminator(code) }
10+
}
11+
12+
function detectLineTerminator(code: string) {
13+
const hasCRLF = /\r\n/.test(code)
14+
const hasLF = /[^\r]\n/.test(code)
15+
16+
return hasCRLF && !hasLF ? '\r\n' : '\n'
17+
}

0 commit comments

Comments
 (0)