Skip to content

Commit 6cef992

Browse files
mischnicfeedthejimclaudeijjk
authored
[backport] normalize CRLF line endings in jscodeshift tests on Windows (#88008) (#88127)
Co-authored-by: Jimmy Lai <laijimmy0@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: JJ Kasper <jj@jjsweb.site>
1 parent 7a94645 commit 6cef992

3 files changed

Lines changed: 116 additions & 0 deletions

File tree

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Enforce LF line endings for all text files (fixes Windows CRLF test flakiness)
2+
* text=auto eol=lf
3+
14
.github/actions/*/dist/** -text linguist-vendored
25
packages/next/bundles/** -text linguist-vendored
36
packages/next/compiled/** -text linguist-vendored

.github/workflows/build_reusable.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ jobs:
117117
input_step_key: ${{ steps.var.outputs.input_step_key }}
118118

119119
steps:
120+
# enforce consistent line endings for git on windows
121+
- name: Configure git to use LF endings
122+
if: ${{ contains(fromJson(inputs.runs_on_labels), 'windows') }}
123+
run: |
124+
git config --global core.autocrlf false
125+
git config --global core.eol lf
126+
shell: bash
127+
120128
- name: Check if fnm is installed
121129
id: check-fnm
122130
run: |

test/jest-setup-after-env.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,111 @@
11
import * as matchers from 'jest-extended'
22
expect.extend(matchers)
33

4+
// Patch jscodeshift testUtils to normalize line endings (fixes Windows CRLF issues)
5+
// The issue: jscodeshift's printer (recast) outputs CRLF on Windows, but test fixtures use LF
6+
// We need to patch both defineTest (which uses internal closure references) and runInlineTest
7+
if (process.platform === 'win32') {
8+
try {
9+
const testUtils = require('jscodeshift/dist/testUtils')
10+
const fs = require('fs')
11+
const path = require('path')
12+
13+
// Helper to normalize line endings
14+
// - Convert CRLF to LF
15+
// - Remove trailing whitespace from each line (not meaningful for code)
16+
// - Ensure exactly one trailing newline (POSIX convention for text files)
17+
// Using \n*$ to handle case where transform output has no trailing newline
18+
const normalizeLF = (str: string) =>
19+
str
20+
.replace(/\r\n/g, '\n')
21+
.replace(/[ \t]+$/gm, '')
22+
.replace(/\n*$/, '\n')
23+
24+
// Patch runInlineTest to normalize both transform output and expected
25+
testUtils.runInlineTest = function (
26+
module: any,
27+
options: any,
28+
input: any,
29+
expectedOutput: string,
30+
testOptions?: any
31+
) {
32+
// Normalize input source
33+
const normalizedInput =
34+
typeof input === 'object' && input.source
35+
? { ...input, source: normalizeLF(input.source) }
36+
: input
37+
// Normalize expected output
38+
const normalizedExpected = normalizeLF(expectedOutput)
39+
40+
// Run the transform and normalize its output for comparison
41+
const output = testUtils.applyTransform(
42+
module,
43+
options,
44+
normalizedInput,
45+
testOptions
46+
)
47+
const normalizedOutput =
48+
typeof output === 'string' ? normalizeLF(output) : output
49+
50+
// Do the comparison ourselves instead of letting the original do it
51+
// eslint-disable-next-line jest/no-standalone-expect -- called from within test blocks
52+
expect(normalizedOutput).toEqual(normalizedExpected)
53+
return normalizedOutput
54+
}
55+
56+
// Replace defineTest entirely since it uses internal closure references
57+
// that bypass our exports patch
58+
testUtils.defineTest = function (
59+
dirName: string,
60+
transformName: string,
61+
options: any,
62+
testFilePrefix?: string,
63+
testOptions?: { parser?: string }
64+
) {
65+
const testName = testFilePrefix
66+
? `transforms correctly using "${testFilePrefix}" data`
67+
: 'transforms correctly'
68+
69+
describe(transformName, () => {
70+
it(testName, () => {
71+
const fixtureDir = path.join(dirName, '..', '__testfixtures__')
72+
const prefix = testFilePrefix || transformName
73+
const module = require(path.join(dirName, '..', transformName))
74+
75+
// Determine file extension based on parser option
76+
const parser = testOptions?.parser || module.parser
77+
const extension =
78+
parser === 'ts' ? 'ts' : parser === 'tsx' ? 'tsx' : 'js'
79+
80+
const inputPath = path.join(
81+
fixtureDir,
82+
`${prefix}.input.${extension}`
83+
)
84+
const outputPath = path.join(
85+
fixtureDir,
86+
`${prefix}.output.${extension}`
87+
)
88+
89+
const source = normalizeLF(fs.readFileSync(inputPath, 'utf8'))
90+
const expectedOutput = normalizeLF(
91+
fs.readFileSync(outputPath, 'utf8')
92+
)
93+
94+
testUtils.runInlineTest(
95+
module,
96+
options,
97+
{ path: inputPath, source },
98+
expectedOutput,
99+
testOptions
100+
)
101+
})
102+
})
103+
}
104+
} catch {
105+
// jscodeshift not available, skip patching
106+
}
107+
}
108+
4109
// A default max-timeout of 90 seconds is allowed
5110
// per test we should aim to bring this down though
6111
jest.setTimeout((process.platform === 'win32' ? 180 : 60) * 1000)

0 commit comments

Comments
 (0)