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