1
+ // gen-langs-map.cjs
2
+ // 将 CodeMirror 的 languages 配置(含动态 import/legacy/sql 包装)
3
+ // 生成一个静态打包用的映射:扩展名 -> 对应加载函数
4
+ // 运行:node gen-langs-map.cjs input.js > langs.generated.ts
5
+
6
+ const fs = require ( 'fs' ) ;
7
+ const path = require ( 'path' ) ;
8
+ const parser = require ( '@babel/parser' ) ;
9
+ const traverse = require ( '@babel/traverse' ) . default ;
10
+ const generate = require ( '@babel/generator' ) . default ;
11
+
12
+ const inputPath = process . argv [ 2 ] ;
13
+ if ( ! inputPath ) {
14
+ console . error ( 'Usage: node gen-langs-map.cjs <input.js>' ) ;
15
+ process . exit ( 1 ) ;
16
+ }
17
+ const src = fs . readFileSync ( inputPath , 'utf8' ) ;
18
+
19
+ const ast = parser . parse ( src , {
20
+ sourceType : 'unambiguous' ,
21
+ plugins : [ 'dynamicImport' ]
22
+ } ) ;
23
+
24
+ function getProp ( node , name ) {
25
+ if ( ! node || node . type !== 'ObjectExpression' ) return null ;
26
+ for ( const p of node . properties ) {
27
+ if ( ( p . key ?. name || p . key ?. value ) === name ) return p ;
28
+ }
29
+ return null ;
30
+ }
31
+
32
+ function litStr ( node ) {
33
+ return node && node . type === 'StringLiteral' ? node . value : null ;
34
+ }
35
+
36
+ function arrStrs ( propNode ) {
37
+ if ( ! propNode ) return [ ] ;
38
+ const val = propNode . value || propNode ; // ObjectProperty vs direct
39
+ const arr = val . type === 'ArrayExpression' ? val : val . value ;
40
+ if ( ! arr || arr . type !== 'ArrayExpression' ) return [ ] ;
41
+ return arr . elements
42
+ . map ( e => ( e && e . type === 'StringLiteral' ? e . value : null ) )
43
+ . filter ( Boolean ) ;
44
+ }
45
+
46
+ function findReturnArg ( fn ) {
47
+ // ObjectMethod / ObjectProperty(FunctionExpression/ArrowFunctionExpression)
48
+ if ( ! fn ) return null ;
49
+ if ( fn . type === 'ObjectMethod' ) {
50
+ const ret = ( fn . body . body || [ ] ) . find ( s => s . type === 'ReturnStatement' ) ;
51
+ return ret ?. argument || null ;
52
+ }
53
+ if ( fn . type === 'ObjectProperty' ) {
54
+ const v = fn . value ;
55
+ if ( v . type === 'ArrowFunctionExpression' || v . type === 'FunctionExpression' ) {
56
+ if ( v . body . type === 'BlockStatement' ) {
57
+ const ret = ( v . body . body || [ ] ) . find ( s => s . type === 'ReturnStatement' ) ;
58
+ return ret ?. argument || null ;
59
+ }
60
+ return v . body ; // concise arrow body
61
+ }
62
+ }
63
+ return null ;
64
+ }
65
+
66
+ // 记录导入:按模块聚合命名导入;以及通配(* as)导入
67
+ const namedImports = new Map ( ) ; // module -> Set(symbol)
68
+ const starImports = new Map ( ) ; // module -> alias
69
+ let needsStreamLanguage = false ;
70
+
71
+ function addNamed ( module , sym ) {
72
+ if ( ! namedImports . has ( module ) ) namedImports . set ( module , new Set ( ) ) ;
73
+ namedImports . get ( module ) . add ( sym ) ;
74
+ }
75
+ function addStar ( module , alias ) {
76
+ starImports . set ( module , alias ) ;
77
+ }
78
+
79
+ function codeFrom ( node ) {
80
+ return generate ( node , { concise : true } ) . code ;
81
+ }
82
+
83
+ function parseLoadSpec ( loadNode ) {
84
+ // 可能是: import('pkg').then(m => m.xxx(...))
85
+ // 或 import('pkg').then(m => legacy(m.xxx(...)))
86
+ // 或 sql("Dialect")
87
+ // 或 直接 return legacy(m.xyz)(几乎不会,但兼容)
88
+ if ( ! loadNode ) return null ;
89
+
90
+ // sql("DialectName")
91
+ if ( loadNode . type === 'CallExpression' && loadNode . callee ?. name === 'sql' ) {
92
+ const dialect = litStr ( loadNode . arguments [ 0 ] ) ;
93
+ if ( ! dialect ) return null ;
94
+ addStar ( '@codemirror/lang-sql' , 'SQL' ) ;
95
+ return { kind : 'sql' , dialect } ;
96
+ }
97
+
98
+ // import('pkg').then(arrow)
99
+ if (
100
+ loadNode . type === 'CallExpression' &&
101
+ loadNode . callee ?. type === 'MemberExpression' &&
102
+ loadNode . callee . property ?. name === 'then'
103
+ ) {
104
+ const importCall = loadNode . callee . object ;
105
+ if (
106
+ importCall ?. type === 'CallExpression' &&
107
+ importCall . callee ?. type === 'Import'
108
+ ) {
109
+ const modArg = importCall . arguments [ 0 ] ;
110
+ const modulePath = litStr ( modArg ) ;
111
+ const thenArg = loadNode . arguments [ 0 ] ;
112
+ // then(m => <expr>)
113
+ let body = thenArg ?. type === 'ArrowFunctionExpression' ? thenArg . body : null ;
114
+ if ( ! body ) return null ;
115
+
116
+ // legacy(m.xxx(...)) or legacy(m.xxx)
117
+ if ( body . type === 'CallExpression' && body . callee ?. name === 'legacy' ) {
118
+ needsStreamLanguage = true ;
119
+ const inner = body . arguments [ 0 ] ;
120
+ if ( inner ?. type === 'MemberExpression' ) {
121
+ const symbol = inner . property . name || inner . property . value ;
122
+ addNamed ( modulePath , symbol ) ;
123
+ return { kind : 'legacy' , modulePath, symbol, call : false , args : [ ] } ;
124
+ }
125
+ if ( inner ?. type === 'CallExpression' && inner . callee . type === 'MemberExpression' ) {
126
+ const symbol = inner . callee . property . name || inner . callee . property . value ;
127
+ const args = inner . arguments . map ( a => codeFrom ( a ) ) ;
128
+ addNamed ( modulePath , symbol ) ;
129
+ return { kind : 'legacy' , modulePath, symbol, call : true , args } ;
130
+ }
131
+ return null ;
132
+ }
133
+
134
+ // m.xxx(...) 或 m.xxx
135
+ if ( body . type === 'CallExpression' && body . callee ?. type === 'MemberExpression' ) {
136
+ const symbol = body . callee . property . name || body . callee . property . value ;
137
+ const args = body . arguments . map ( a => codeFrom ( a ) ) ;
138
+ addNamed ( modulePath , symbol ) ;
139
+ return { kind : 'modern' , modulePath, symbol, args } ;
140
+ }
141
+ if ( body . type === 'MemberExpression' ) {
142
+ const symbol = body . property . name || body . property . value ;
143
+ addNamed ( modulePath , symbol ) ;
144
+ return { kind : 'modern' , modulePath, symbol, args : [ ] } ;
145
+ }
146
+ }
147
+ }
148
+
149
+ // 兜底:可能直接 legacy(m.xxx)
150
+ if ( loadNode . type === 'CallExpression' && loadNode . callee ?. name === 'legacy' ) {
151
+ needsStreamLanguage = true ;
152
+ const inner = loadNode . arguments [ 0 ] ;
153
+ if ( inner ?. type === 'MemberExpression' ) {
154
+ const symbol = inner . property . name || inner . property . value ;
155
+ // 模块不详,无法导入
156
+ return { kind : 'legacy' , modulePath : null , symbol, call : false , args : [ ] } ;
157
+ }
158
+ }
159
+
160
+ return null ;
161
+ }
162
+
163
+ let languagesArray = null ;
164
+
165
+ traverse ( ast , {
166
+ VariableDeclarator ( path ) {
167
+ const id = path . node . id ;
168
+ if ( id . type === 'Identifier' && id . name === 'languages' ) {
169
+ if ( path . node . init && path . node . init . type === 'ArrayExpression' ) {
170
+ languagesArray = path . node . init ;
171
+ }
172
+ }
173
+ }
174
+ } ) ;
175
+
176
+ if ( ! languagesArray ) {
177
+ console . error ( '未找到变量 `languages` 的数组定义。' ) ;
178
+ process . exit ( 1 ) ;
179
+ }
180
+
181
+ const entries = [ ] ; // [{ext, expr}]
182
+ for ( const el of languagesArray . elements ) {
183
+ if ( ! el ) continue ;
184
+ // 期望是 language.LanguageDescription.of({ ... })
185
+ if ( el . type !== 'CallExpression' ) continue ;
186
+
187
+ // 取对象参数
188
+ const obj = el . arguments ?. [ 0 ] ;
189
+ if ( ! obj || obj . type !== 'ObjectExpression' ) continue ;
190
+
191
+ const exts = arrStrs ( getProp ( obj , 'extensions' ) ) ; // 只用 extensions 做键
192
+ if ( ! exts . length ) continue ; // 没有扩展名就无法生成键(例如 Dockerfile/Nginx 等)
193
+
194
+ // load 方法
195
+ const loadProp = getProp ( obj , 'load' ) ;
196
+ const retArg = findReturnArg ( loadProp ) ;
197
+ const spec = parseLoadSpec ( retArg ) ;
198
+
199
+ if ( ! spec ) continue ;
200
+
201
+ // 生成每个扩展名对应的表达式字符串
202
+ for ( const ext of exts ) {
203
+ let expr ;
204
+ if ( spec . kind === 'sql' ) {
205
+ // 例如: () => SQL.sql({ dialect: SQL.SQLite })
206
+ expr = `() => SQL.sql({ dialect: SQL.${ spec . dialect } })` ;
207
+ } else if ( spec . kind === 'modern' ) {
208
+ // 例如: () => javascript({ jsx: true })
209
+ const callArgs = spec . args . length ? `(${ spec . args . join ( ',' ) } )` : '()' ;
210
+ expr = `() => ${ spec . symbol } ${ callArgs } ` ;
211
+ } else if ( spec . kind === 'legacy' ) {
212
+ // 例如: () => StreamLanguage.define(perl) 或 define(asn1({}))
213
+ const inner = spec . call
214
+ ? `${ spec . symbol } (${ spec . args . join ( ',' ) } )`
215
+ : spec . symbol ;
216
+ expr = `() => StreamLanguage.define(${ inner } )` ;
217
+ if ( spec . modulePath ) {
218
+ addNamed ( spec . modulePath , spec . symbol ) ;
219
+ }
220
+ } else {
221
+ continue ;
222
+ }
223
+
224
+ entries . push ( [ ext , expr ] ) ;
225
+ }
226
+ }
227
+
228
+ entries . push ( [ 'solidity' , `() => solidity` ] ) ;
229
+ entries . push ( [ 'nix' , `() => nix()` ] ) ;
230
+ entries . push ( [ 'svelte' , `() => svelte()` ] ) ;
231
+
232
+ // 组装 import 语句
233
+ const importLines = [ ] ;
234
+ if ( needsStreamLanguage ) {
235
+ importLines . push ( `import { StreamLanguage, LanguageSupport } from '@codemirror/language';` ) ;
236
+ }
237
+
238
+ for ( const [ mod , set ] of namedImports ) {
239
+ if ( mod === '@codemirror/lang-sql' ) continue ; // 由星号导入覆盖
240
+ const names = Array . from ( set ) . sort ( ) ;
241
+ importLines . push ( `import { ${ names . join ( ', ' ) } } from '${ mod } ';` ) ;
242
+ }
243
+ for ( const [ mod , alias ] of starImports ) {
244
+ importLines . push ( `import * as ${ alias } from '${ mod } ';` ) ;
245
+ }
246
+
247
+ importLines . push ( `import { nix } from '@replit/codemirror-lang-nix';` ) ;
248
+ importLines . push ( `import { svelte } from '@replit/codemirror-lang-svelte';` ) ;
249
+ importLines . push ( `import { solidity } from '@replit/codemirror-lang-solidity';` ) ;
250
+
251
+ // 去重 & 排序键
252
+ const mapObj = new Map ( ) ;
253
+ for ( const [ k , v ] of entries ) mapObj . set ( k , v ) ; // 后者覆盖前者
254
+ const sorted = Array . from ( mapObj . entries ( ) ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) ;
255
+
256
+ // 输出文件内容
257
+ let out = '' ;
258
+ out += `// auto-generated by gen-langs-map.cjs – DO NOT EDIT\n` ;
259
+ out += importLines . join ( '\n' ) + ( importLines . length ? '\n\n' : '' ) ;
260
+ out += `export const langs: Record<string, () => LanguageSupport | StreamLanguage<unknown>> = {\n` ;
261
+ for ( const [ k , v ] of sorted ) {
262
+ const key = / ^ [ a - z 0 - 9 _ + - ] + $ / i. test ( k ) ? k : JSON . stringify ( k ) ;
263
+ out += ` ${ JSON . stringify ( k ) } : ${ v } ,\n` ;
264
+ }
265
+ out += `};\n\n` ;
266
+
267
+ out += `export const langNames = Object.keys(langs) as LanguageName[];\n\n` ;
268
+ out += `export type LanguageName = keyof typeof langs;\n` ;
269
+ out += `export function loadLanguage(name: LanguageName) {\n` ;
270
+ out += ` return langs[name] ? langs[name]() : null;\n` ;
271
+ out += `}\n` ;
272
+
273
+ process . stdout . write ( out ) ;
0 commit comments