#!/usr/bin/env node
* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createCommand } from "commander"
import * as fs from "fs"
import * as path from "path"
import {
DefaultIDLLinterOptions,
fromIDL,
idlToDtsString,
findVersion,
formatInputPaths,
setDefaultConfiguration,
Language,
verifyIDLLinter,
PeerLibrary,
scanInputDirs,
parseIDLFile,
IDLLinterError,
InteropModuleType,
validatePaths,
} from "@idlizer/core"
import {
IDLFile,
toIDLString
} from "@idlizer/core/idl"
import { IDLVisitor } from "./IDLVisitor.js"
import { runPreprocessor } from "./preprocessor.js"
import { DtsgenConfiguration, dtsgenDefaultConfigurationPaths, loadDtsgenConfiguration } from "./config.js"
import { generate } from "./idlize.js"
import { defaultCompilerOptions } from "./util.js"
import { fileURLToPath } from 'url'
const DIR_NAME = path.resolve(fileURLToPath(import.meta.url), "../../../..");
const command = createCommand()
.option('--dts2idl', 'Convert .d.ts to IDL definitions')
.option('--idl2dts', 'Convert IDL to .d.ts definitions')
.option('--lint', 'Verifies input and exits')
.option('--run-preprocessor', 'Runs preprocessor')
.option('--input-dir <path>', 'Path to input dir(s), comma separated')
.option('--aux-input-dir <path>', 'Path to aux input dir(s), comma separated')
.option('--base-dir <path>', 'Base directories, for the purpose of packetization of IDL modules, comma separated, defaulted to --input-dir if missing')
.option('--output-dir <path>', 'Path to output dir')
.option('--input-files <files...>', 'Comma-separated list of specific files to process')
.option('--aux-input-files <files...>', 'Comma-separated list of specific aux files to process')
.option('--verbose', 'Verbose processing')
.option('--verify-idl', 'Verify produced IDL')
.option('--docs [all|opt|none]', 'How to handle documentation: include, optimize, or skip')
.option('--language [ts|sts|cangjie]', 'Output language')
.option('--version')
.option('--plugin <file>', 'File with generator\'s plugin')
.option('--default-idl-package <name>', 'Name of the default package for generated IDL')
.option('--enable-log', 'Enable logging')
.option('--options-file <path...>', 'Paths to generator configuration options file (appends to defaults). Use --ignore-default-config to override default options.')
.option('--ignore-default-config', 'Use with --options-file to override default generator configuration options.', false)
.option('--arkts-extension <string> [.ts|.ets]', "Generated ArkTS language files extension.", ".ts")
const options = command
.parse()
.opts()
function main() {
if (options.runPreprocessor) {
const resolved = path.resolve(options.inputDir)
runPreprocessor([resolved], options.outputDir)
return
}
Language.ARKTS.extension = options.arktsExtension as string
const dtsConf: DtsgenConfiguration = loadDtsgenConfiguration([
...(options.ignoreDefaultConfig ? [] : dtsgenDefaultConfigurationPaths()),
...(options.optionsFile ?? []),
])
setDefaultConfiguration(dtsConf)
if (process.env.npm_package_version) {
console.log(`IDLize version ${findVersion()}`)
}
const { baseDirs, inputDirs, auxInputDirs, inputFiles, auxInputFiles } = formatInputPaths(options)
validatePaths(baseDirs, "dir")
validatePaths(inputDirs, "dir")
validatePaths(auxInputDirs, "dir")
validatePaths(inputFiles, "file")
validatePaths(auxInputFiles, "file")
const dtsInputFiles = scanInputDirs(inputDirs).concat(inputFiles)
const dtsAuxInputFiles = auxInputFiles
if (options.lint) {
const files = dtsInputFiles.map((filePath) => {
return parseIDLFile(filePath)
})
const resolver = new PeerLibrary(Language.TS, InteropModuleType)
resolver.files = files
let totalErrors = 0
const errorRecords: [string, number][] = []
files.forEach((file) => {
try {
verifyIDLLinter(file, resolver, {
checkEnumsConsistency: true,
checkReferencesResolved: true,
validEntryAttributes: DefaultIDLLinterOptions.validEntryAttributes
})
} catch (error) {
if (error instanceof IDLLinterError) {
totalErrors += error.size
errorRecords.push([file.fileName ?? '', error.size])
console.error(error.message)
return
}
throw error
}
})
if (totalErrors > 0) {
process.exitCode = -1
console.error()
errorRecords.forEach(([fileName, errorNumber]) => {
console.error(`${errorNumber.toString().padStart(5, ' ')} ${fileName}`)
})
console.error(' ----------------------------')
console.error(` Total errors: ${totalErrors}`)
}
return
}
if (options.dts2idl) {
const { inputDirs, inputFiles } = formatInputPaths(options)
validatePaths(inputDirs, 'dir')
validatePaths(inputFiles, 'file')
const idlLibrary = new PeerLibrary(Language.TS, InteropModuleType)
generate(
baseDirs,
[...inputDirs, ...auxInputDirs],
dtsInputFiles,
dtsAuxInputFiles,
options.outputDir ?? "./idl",
path.resolve(DIR_NAME, "stdlib.d.ts"),
(sourceFile, program, compilerHost) => new IDLVisitor(
baseDirs, sourceFile, program, compilerHost, options, dtsConf).goNewMode(),
{
compilerOptions: defaultCompilerOptions,
onSingleFile: (file: IDLFile, outputDir, sourceFile, isAux) => {
let fileName = sourceFile.fileName
baseDirs.forEach(dir => {
const nextFileName = path.relative(path.resolve(dir), sourceFile.fileName)
if (nextFileName.length < fileName.length) {
fileName = nextFileName
}
})
const fileBaseName = path.basename(sourceFile.fileName)
if (fileBaseName === "stdlib.d.ts")
return
const basename = fileName.replace(/^\.*(\/|\\)/, '').replaceAll(path.sep, '.')
console.log('producing', basename)
const outFile = path.join(
outputDir,
basename.replace(".d.ts", ".idl")
)
console.log("saved", outFile)
const generated = toIDLString(file, {
disableEnumInitializers: options.disableEnumInitializers ?? false
})
if (options.verbose) {
console.log(generated)
}
if (!fs.existsSync(path.dirname(outFile))) {
fs.mkdirSync(path.dirname(outFile), { recursive: true })
}
fs.writeFileSync(outFile, generated)
if (options.verifyIdl) {
verifyIDLLinter(parseIDLFile(generated), idlLibrary, DefaultIDLLinterOptions)
}
if (isAux)
idlLibrary.auxFiles.push(file)
else
idlLibrary.files.push(file)
},
onEnd(outDir: string) {
if (options.verifyIdl) {
idlLibrary.files.forEach(file => {
verifyIDLLinter(file, idlLibrary, DefaultIDLLinterOptions)
})
}
},
}
)
return
}
if (options.idl2dts) {
const generatedDtsDir = options.outputDir ?? "./generated/dts/"
if (options.inputFiles && typeof options.inputFiles === 'string') {
options.inputFiles = options.inputFiles
.split(',')
.map(file => file.trim())
.filter(Boolean)
}
const inputDirs = options.inputDir
if (typeof options.inputDir === 'string') {
options.inputDir = options.inputDir.split(',')
.map(dir => dir.trim())
.filter(Boolean)
}
const inputFiles: string[] = options.inputFiles || []
inputFiles.forEach(file => {
if (!fs.existsSync(file)) {
console.error(`Input file does not exist: ${file}`)
process.exit(1)
} else {
console.log(`Input file exists: ${file}`)
}
})
fromIDL(
inputDirs,
inputFiles,
generatedDtsDir,
".d.ts",
options.verbose ?? false,
idlToDtsString
)
return
}
command.help()
}
main()