-
-
Notifications
You must be signed in to change notification settings - Fork 82
fix(android,flutter): Preserve CRLF line endings when present #1253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c75b764
3ecf364
2e413cd
8e5636e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import * as fs from 'fs'; | ||
| import * as path from 'path'; | ||
| import { getUncommittedOrUntrackedFiles } from './git'; | ||
|
|
||
| /** | ||
| * Fixes mixed line endings in files modified by the wizard. | ||
| * | ||
| * When the wizard reads a CRLF file and inserts content with hardcoded \n, | ||
| * the result is mixed line endings. This function detects files with CRLF | ||
| * and normalizes all line endings to CRLF. | ||
| * | ||
| * Call this at the end of a wizard run, similar to runPrettierIfInstalled(). | ||
| */ | ||
| const TEXT_EXTENSIONS = new Set([ | ||
| '.dart', | ||
| '.yaml', | ||
| '.yml', | ||
| '.properties', | ||
| '.java', | ||
| '.kt', | ||
| '.xml', | ||
| '.gradle', | ||
| '.kts', | ||
| '.gitignore', | ||
| '.json', | ||
| ]); | ||
|
|
||
| export function fixLineEndings(): void { | ||
| const files = getUncommittedOrUntrackedFiles() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The new Suggested FixModify Prompt for AI Agent |
||
| .map((f) => (f.startsWith('- ') ? f.slice(2) : f)) | ||
| .filter(Boolean); | ||
|
|
||
| for (const file of files) { | ||
| const filePath = path.resolve(file); | ||
|
|
||
| if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { | ||
| continue; | ||
| } | ||
|
|
||
| const ext = path.extname(filePath).toLowerCase(); | ||
| const basename = path.basename(filePath); | ||
| if (!TEXT_EXTENSIONS.has(ext) && !TEXT_EXTENSIONS.has(basename)) { | ||
| continue; | ||
| } | ||
|
|
||
| const content = fs.readFileSync(filePath, 'utf8'); | ||
|
|
||
| if (!content.includes('\r\n')) { | ||
| continue; | ||
| } | ||
|
|
||
| // File has CRLF — normalize all line endings to CRLF | ||
| const normalized = content.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); | ||
| fs.writeFileSync(filePath, normalized, 'utf8'); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; | ||
| import * as fs from 'fs'; | ||
| import * as path from 'path'; | ||
| import * as os from 'os'; | ||
| import { fixLineEndings } from '../../src/utils/line-endings'; | ||
| import * as git from '../../src/utils/git'; | ||
|
|
||
| describe('fixLineEndings', () => { | ||
| let tmpDir: string; | ||
|
|
||
| beforeEach(() => { | ||
| tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'line-endings-test-')); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| fs.rmSync(tmpDir, { recursive: true }); | ||
| vi.restoreAllMocks(); | ||
| }); | ||
|
|
||
| it('normalizes mixed line endings to CRLF when file has CRLF', () => { | ||
| const filePath = path.join(tmpDir, 'mixed.dart'); | ||
| fs.writeFileSync(filePath, 'line1\r\nline2\nline3\r\n', 'utf8'); | ||
|
|
||
| vi.spyOn(git, 'getUncommittedOrUntrackedFiles').mockReturnValue([ | ||
| `- ${filePath}`, | ||
| ]); | ||
|
|
||
| fixLineEndings(); | ||
|
|
||
| const result = fs.readFileSync(filePath, 'utf8'); | ||
| expect(result).toBe('line1\r\nline2\r\nline3\r\n'); | ||
| }); | ||
|
|
||
| it('skips files that are pure LF', () => { | ||
| const filePath = path.join(tmpDir, 'lf.dart'); | ||
| const original = 'line1\nline2\n'; | ||
| fs.writeFileSync(filePath, original, 'utf8'); | ||
|
|
||
| vi.spyOn(git, 'getUncommittedOrUntrackedFiles').mockReturnValue([ | ||
| `- ${filePath}`, | ||
| ]); | ||
|
|
||
| fixLineEndings(); | ||
|
|
||
| const result = fs.readFileSync(filePath, 'utf8'); | ||
| expect(result).toBe(original); | ||
| }); | ||
|
|
||
| it('leaves consistent CRLF files unchanged', () => { | ||
| const filePath = path.join(tmpDir, 'crlf.dart'); | ||
| const original = 'line1\r\nline2\r\n'; | ||
| fs.writeFileSync(filePath, original, 'utf8'); | ||
|
|
||
| vi.spyOn(git, 'getUncommittedOrUntrackedFiles').mockReturnValue([ | ||
| `- ${filePath}`, | ||
| ]); | ||
|
|
||
| fixLineEndings(); | ||
|
|
||
| const result = fs.readFileSync(filePath, 'utf8'); | ||
| expect(result).toBe(original); | ||
| }); | ||
|
|
||
| it('skips non-text files', () => { | ||
| const filePath = path.join(tmpDir, 'image.png'); | ||
| const original = 'line1\r\nline2\nline3\r\n'; | ||
| fs.writeFileSync(filePath, original, 'utf8'); | ||
|
|
||
| vi.spyOn(git, 'getUncommittedOrUntrackedFiles').mockReturnValue([ | ||
| `- ${filePath}`, | ||
| ]); | ||
|
|
||
| fixLineEndings(); | ||
|
|
||
| const result = fs.readFileSync(filePath, 'utf8'); | ||
| expect(result).toBe(original); | ||
| }); | ||
|
|
||
| it('skips directories', () => { | ||
| const dirPath = path.join(tmpDir, 'subdir'); | ||
| fs.mkdirSync(dirPath); | ||
|
|
||
| vi.spyOn(git, 'getUncommittedOrUntrackedFiles').mockReturnValue([ | ||
| `- ${dirPath}`, | ||
| ]); | ||
|
|
||
| expect(() => fixLineEndings()).not.toThrow(); | ||
| }); | ||
|
|
||
| it('skips nonexistent files', () => { | ||
| vi.spyOn(git, 'getUncommittedOrUntrackedFiles').mockReturnValue([ | ||
| '- nonexistent.txt', | ||
| ]); | ||
|
|
||
| expect(() => fixLineEndings()).not.toThrow(); | ||
| }); | ||
|
|
||
| it('does nothing when there are no modified files', () => { | ||
| vi.spyOn(git, 'getUncommittedOrUntrackedFiles').mockReturnValue([]); | ||
|
|
||
| expect(() => fixLineEndings()).not.toThrow(); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.