Skip to content

Commit 361de05

Browse files
committed
chore: organize imports for generated TS without formatting (no style changes)
1 parent 565d19b commit 361de05

File tree

3 files changed

+118
-3
lines changed

3 files changed

+118
-3
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ format-check:
1616

1717
format-generated:
1818
pnpm install
19-
# Remove unused imports and format generated TS without linting
20-
pnpm biome check --write --linter-enabled=false packages_generated/
19+
# Organize imports only (no style changes) for generated TS
20+
pnpm dlx tsx ./scripts/organizeGeneratedImports.ts
2121

2222
typing:
2323
pnpm run typecheck

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"doc": "typedoc",
2626
"format": "biome format --write .",
2727
"format:check": "biome format .",
28-
"format:generated": "biome check --write --linter-enabled=false packages_generated",
28+
"format:generated": "pnpm dlx tsx ./scripts/organizeGeneratedImports.ts",
2929
"prettier": "prettier --write '**/*.{md,mdx,yml,yaml}'",
3030
"lint": "biome lint .",
3131
"check": "biome check .",
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env tsx
2+
3+
/**
4+
* Organize imports (remove unused, sort/group) for generated TS files only,
5+
* without applying any formatter or linter rules.
6+
*
7+
* Scope: packages_generated/**
8+
*/
9+
10+
import { readFileSync, writeFileSync } from 'node:fs'
11+
import { dirname, resolve } from 'node:path'
12+
import { globby } from 'globby'
13+
import ts from 'typescript'
14+
15+
async function main(): Promise<void> {
16+
const rootDir = resolve(process.cwd(), 'packages_generated')
17+
const files = await globby(['**/*.ts'], { cwd: rootDir, absolute: true })
18+
if (files.length === 0) return
19+
20+
// Create a LanguageService over the generated files to enable organizeImports
21+
const fileTextCache = new Map<string, string>()
22+
23+
for (const file of files) {
24+
fileTextCache.set(file, readFileSync(file, 'utf8'))
25+
}
26+
27+
const compilerOptions: ts.CompilerOptions = {
28+
target: ts.ScriptTarget.ES2022,
29+
module: ts.ModuleKind.ESNext,
30+
moduleResolution: ts.ModuleResolutionKind.Bundler,
31+
skipLibCheck: true,
32+
allowJs: false,
33+
resolveJsonModule: true,
34+
isolatedModules: false,
35+
declaration: false,
36+
noEmit: true,
37+
jsx: ts.JsxEmit.Preserve,
38+
types: [],
39+
baseUrl: process.cwd(),
40+
}
41+
42+
const servicesHost: ts.LanguageServiceHost = {
43+
getScriptFileNames: () => Array.from(fileTextCache.keys()),
44+
getScriptVersion: () => '1',
45+
getScriptSnapshot: fileName => {
46+
const text = fileTextCache.get(fileName)
47+
if (text === undefined) return undefined
48+
return ts.ScriptSnapshot.fromString(text)
49+
},
50+
getCurrentDirectory: () => process.cwd(),
51+
getCompilationSettings: () => compilerOptions,
52+
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
53+
readFile: fileName => {
54+
try {
55+
return readFileSync(fileName, 'utf8')
56+
} catch {
57+
return undefined
58+
}
59+
},
60+
fileExists: ts.sys.fileExists,
61+
directoryExists: ts.sys.directoryExists,
62+
getDirectories: ts.sys.getDirectories,
63+
}
64+
65+
const languageService = ts.createLanguageService(
66+
servicesHost,
67+
ts.createDocumentRegistry(),
68+
)
69+
70+
for (const fileName of fileTextCache.keys()) {
71+
// Apply organizeImports for the single-file scope
72+
const changes =
73+
languageService.organizeImports(
74+
{ type: 'file', fileName },
75+
{ newLineCharacter: '\n' },
76+
) ?? []
77+
78+
if (changes.length === 0) continue
79+
80+
// Apply text changes in memory
81+
let text = fileTextCache.get(fileName) as string
82+
for (const change of changes
83+
.flatMap(c => c.textChanges)
84+
.sort((a, b) => b.span.start - a.span.start)) {
85+
const start = change.span.start
86+
const end = start + change.span.length
87+
text = text.slice(0, start) + change.newText + text.slice(end)
88+
}
89+
90+
fileTextCache.set(fileName, text)
91+
}
92+
93+
// Persist only files that changed
94+
let updated = 0
95+
for (const [file, text] of fileTextCache) {
96+
const original = readFileSync(file, 'utf8')
97+
if (original !== text) {
98+
// Ensure output directory exists (should already exist for generated files)
99+
dirname(file)
100+
writeFileSync(file, text, 'utf8')
101+
updated += 1
102+
}
103+
}
104+
105+
// eslint-disable-next-line no-console
106+
console.log(
107+
`Organized imports for ${updated} file(s) under packages_generated`,
108+
)
109+
}
110+
111+
main().catch(error => {
112+
// eslint-disable-next-line no-console
113+
console.error(error)
114+
process.exit(1)
115+
})

0 commit comments

Comments
 (0)