16
import { program } from "commander"
17
import * as fs from "fs"
18
import * as path from "path"
19
import { fromIDL, scanIDL } from "./from-idl/common"
20
import { idlToString } from "./from-idl/DtsPrinter"
21
import { generate } from "./idlize"
22
import { IDLEntry, forEachChild, toIDLString } from "./idl"
23
import { printHeader, toHeaderString, wrapWithPrologueAndEpilogue } from "./idl2h"
24
import { LinterMessage, LinterVisitor, toLinterString } from "./linter"
25
import { CompileContext, IDLVisitor } from "./IDLVisitor"
26
import { TestGeneratorVisitor } from "./TestGeneratorVisitor"
37
nativeModuleDeclaration,
38
nativeModuleEmptyDeclaration
39
} from "./peer-generation/FileGenerators"
42
} from "./peer-generation/PeerGeneratorVisitor"
43
import { defaultCompilerOptions, isDefined, renameDtsToPeer, stringOrNone, toSet } from "./util"
44
import { TypeChecker } from "./typecheck"
45
import { SortingEmitter } from "./peer-generation/SortingEmitter"
46
import { initRNG } from "./rand_utils"
48
const options = program
49
.option('--dts2idl', 'Convert .d.ts to IDL definitions')
50
.option('--dts2h', 'Convert .d.ts to .h definitions')
51
.option('--dts2test', 'Generate tests from .d.ts to .h')
52
.option('--dts2peer', 'Convert .d.ts to peer drafts')
53
.option('--ets2ts', 'Convert .ets to .ts')
54
.option('--input-dir <path>', 'Path to input dir')
55
.option('--output-dir <path>', 'Path to output dir')
56
.option('--input-file <name>', 'Name of file to convert, all files in input-dir if none')
57
.option('--idl2dts', 'Convert IDL to .d.ts definitions')
58
.option('--idl2h', 'Convert IDL to .h definitions')
59
.option('--linter', 'Run linter')
60
.option('--linter-suppress-errors <suppress>', 'Error codes to suppress, comma separated, no space')
61
.option('--linter-whitelist <whitelist.json>', 'Whitelist for linter')
62
.option('--verbose', 'Verbose processing')
63
.option('--verify-idl', 'Verify produced IDL')
64
.option('--common-to-attributes', 'Transform common attributes as IDL attributes')
65
.option('--test-interface <name>', 'Interfaces to test (comma separated)')
66
.option('--test-method <name>', 'Methods to test (comma separated)')
67
.option('--test-property <name>', 'Properties to test (comma separated)')
68
.option('--generate-interface <name>', 'Interfaces to generate (comma separated)')
69
.option('--disable-enum-initializers', "Don't include enum member initializers in the interface")
70
.option('--native-bridge-path <name>', "Path to native bridge")
71
.option('--dump-serialized', "Dump serialized data")
72
.option('--docs [all|opt|none]', 'How to handle documentation: include, optimize, or skip')
77
function findVersion() {
78
if (process.env.npm_package_version) return process.env.npm_package_version
79
let packageJson = path.join(__dirname, '..', 'package.json')
81
let json = fs.readFileSync(packageJson).toString()
82
return json ? JSON.parse(json).version : undefined
88
if (process.env.npm_package_version) {
89
console.log(`IDLize version ${findVersion()}`)
95
const tsCompileContext = new CompileContext()
99
options.outputDir ?? "./idl",
100
(sourceFile, typeChecker) => new IDLVisitor(sourceFile, typeChecker, tsCompileContext, options),
102
compilerOptions: defaultCompilerOptions,
103
onSingleFile: (entries: IDLEntry[], outputDir, sourceFile) => {
104
const outFile = path.join(outputDir,
105
path.basename(sourceFile.fileName).replace(".d.ts", ".idl"))
106
console.log("producing", outFile)
107
if (options.skipDocs) {
108
entries.forEach(it => forEachChild(
109
it, (it) => it.documentation = undefined))
111
let generated = toIDLString(entries, {
112
verifyIdl: options.verifyIdl ?? false,
113
disableEnumInitializers: options.disableEnumInitializers ?? false
115
if (options.verbose) console.log(generated)
116
fs.writeFileSync(outFile, generated)
124
const allEntries = new Array<IDLEntry[]>()
125
const tsCompileContext = new CompileContext()
129
options.outputDir ?? "./headers",
130
(sourceFile, typeChecker) => new IDLVisitor(sourceFile, typeChecker, tsCompileContext, options.commonToAttributes ?? false),
132
compilerOptions: defaultCompilerOptions,
133
onSingleFile: (entries: IDLEntry[]) => allEntries.push(entries),
136
const outFile = path.join(options.outputDir ?? "./headers", "arkoala_api.h")
137
console.log("producing", outFile)
139
const generated = toHeaderString(new TypeChecker(allEntries.flat()), allEntries, options.generateInterface)
140
if (options.verbose) console.log(generated)
141
fs.writeFileSync(outFile, generated)
146
const allEntries = new Array<LinterMessage[]>()
151
(sourceFile, typeChecker) => new LinterVisitor(sourceFile, typeChecker),
153
compilerOptions: defaultCompilerOptions,
154
onSingleFile: (entries: LinterMessage[]) => allEntries.push(entries),
156
onEnd: (outputDir) => {
157
const outFile = options.outputDir ? path.join(outputDir, "linter.txt") : undefined
158
let [generated, exitCode, histogram] = toLinterString(allEntries, options.linterSuppressErrors, options.linterWhitelist)
159
console.log(histogram)
160
if (!outFile || options.verbose) console.log(generated)
161
if (outFile) fs.writeFileSync(outFile, generated)
162
process.exit(exitCode)
169
if (options.dts2test) {
171
let testInterfaces = options.testInterface
172
if (testInterfaces === undefined) {
173
function fileNameToClass(name: string): string {
176
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
180
let inDir = path.resolve(options.inputDir)
181
testInterfaces = fs.readdirSync(inDir)
182
.filter(file => file.endsWith("d.ts"))
183
.map(file => file.substring(0, file.length - 5))
184
.map(fileNameToClass)
188
let lines: string[] = []
192
options.outputDir ?? "./tests",
193
(sourceFile, typeChecker) => new TestGeneratorVisitor(sourceFile, typeChecker, testInterfaces, options.testMethod, options.testProperties),
195
compilerOptions: defaultCompilerOptions,
196
onBegin: (outDir: string) => {
198
onSingleFile: (entries: string[], outputDir, sourceFile) => {
199
lines = lines.concat(entries)
201
onEnd: (outDir: string) => {
202
let generated = lines.join("\n")
203
const outFile = path.join(outDir, "index.ts")
204
if (options.verbose) {
205
console.log(generated)
207
console.log(`Write fuzzing peers to file ${outFile}`)
208
fs.writeFileSync(outFile, lines.join("\n"))
215
if (options.idl2dts) {
219
options.outputDir ?? "./dts/",
221
options.verbose ?? false,
228
const idlFiles = scanIDL(
232
const typeChecker = new TypeChecker(idlFiles.flat())
233
const body = idlFiles
234
.flatMap(it => printHeader(typeChecker, it, toSet(options.generateInterface)))
236
.filter(it => it.length > 0)
238
const generatedHeader = wrapWithPrologueAndEpilogue(body)
239
if (options.verbose) {
242
const outputDir = options.outputDir ?? "./headers"
243
if (!fs.existsSync(outputDir)) {
244
fs.mkdirSync(outputDir, { recursive: true })
246
const outFile = path.join(outputDir, "arkoala_api.h")
247
console.log("producing", outFile)
248
fs.writeFileSync(outFile, generatedHeader)
252
if (options.dts2peer) {
253
const nativeMethods: string[] = []
254
const nativeEmptyMethods: string[] = []
255
const bridgeCcArray: string[] = []
256
const deserializerC: string[] = []
257
const structsC = new SortingEmitter()
258
const structsForwardC: string[] = []
259
const serializerTS: string[] = []
260
const apiHeaders: string[] = []
261
const apiHeadersList: string[] = []
262
const dummyImpl: string[] = []
263
const dummyImplModifiers: string[] = []
264
const dummyImplModifierList: string[] = []
269
options.outputDir ?? "./peers",
270
(sourceFile, typeChecker) => new PeerGeneratorVisitor({
271
sourceFile: sourceFile,
272
typeChecker: typeChecker,
273
interfacesToGenerate: toSet(options.generateInterface),
274
nativeModuleMethods: nativeMethods,
275
nativeModuleEmptyMethods: nativeEmptyMethods,
276
outputC: bridgeCcArray,
277
outputSerializersTS: serializerTS,
278
outputSerializersC: deserializerC,
279
outputStructsForwardC: structsForwardC,
280
outputStructsC: structsC,
281
apiHeaders: apiHeaders,
282
apiHeadersList: apiHeadersList,
283
dummyImpl: dummyImpl,
284
dummyImplModifiers: dummyImplModifiers,
285
dummyImplModifierList: dummyImplModifierList,
286
dumpSerialized: options.dumpSerialized ?? false,
289
compilerOptions: defaultCompilerOptions,
290
onSingleFile: (entries: stringOrNone[], outputDir, sourceFile) => {
291
const outFile = path.join(
293
renameDtsToPeer(path.basename(sourceFile.fileName))
295
if (entries.length > 0) {
296
console.log("producing", outFile)
297
let generated = entries
298
.filter(element => (element?.length ?? 0) > 0)
300
if (options.verbose) console.log(generated)
301
fs.writeFileSync(outFile, generated)
304
onEnd(outDir: string) {
306
path.join(outDir, 'NativeModule.ts'),
307
nativeModuleDeclaration(nativeMethods, options.nativeBridgeDir ?? "../../../../native/NativeBridge", false)
310
path.join(outDir, 'NativeModuleEmpty.ts'),
311
nativeModuleEmptyDeclaration(nativeEmptyMethods)
313
const bridgeCc = bridgeCcDeclaration(bridgeCcArray)
314
fs.writeFileSync(path.join(outDir, 'bridge.cc'), bridgeCc)
315
fs.writeFileSync(path.join(outDir, 'Serializer.ts'), makeTSSerializer(serializerTS))
316
fs.writeFileSync(path.join(outDir, 'Deserializer.h'), makeCDeserializer(structsForwardC, structsC.getOutput(), deserializerC))
317
fs.writeFileSync(path.join(outDir, 'arkoala_api.h'), makeApiHeaders(apiHeaders) + makeApiModifiers(apiHeadersList))
319
dummyImplementations(dummyImpl) +
320
dummyModifiers(dummyImplModifiers) +
321
dummyModifierList(dummyImplModifierList)
322
fs.writeFileSync(path.join(outDir, 'dummy_impl.cc'), dummyImplCc)
323
copyPeerLib(path.join(__dirname, '..', 'peer_lib'), outDir)