* 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 * as ts from "typescript"
import { LinterVisitor, toLinterString } from "./linter"
import { LinterMessage } from "./LinterMessage"
import { patchDefaultConfiguration, findVersion, generate, scanInputDirs } from "@idlizer/core"
const options = createCommand()
.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('-r,--recursive', 'Scan input directory recursively', false)
.option('--input-file <name>', 'Name of file to convert, all files in input-dir if none')
.option('--suppress-errors <suppress>', 'Error codes to suppress, comma separated, no space')
.option('--whitelist <whitelist.json>', 'Whitelist for linter')
.parse()
.opts()
const defaultCompilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
noLib: true,
types: []
}
function processInputOption(option: string | undefined): string[] {
if (!option) return []
if (typeof option === 'string') {
return option.split(',')
.map(item => item.trim())
.filter(Boolean)
}
return []
}
export type InputPaths = {
baseDirs: string[]
inputDirs: string[]
auxInputDirs: string[]
inputFiles: string[]
auxInputFiles: string[]
libraryPackages: string[]
}
function formatInputPaths(options: any): InputPaths {
if (options.inputFiles && typeof options.inputFiles === 'string') {
options.inputFiles = processInputOption(options.inputFiles)
}
if (options.auxInputFiles && typeof options.auxInputFiles === 'string') {
options.auxInputFiles = processInputOption(options.auxInputFiles)
}
if (options.inputDir && typeof options.inputDir === 'string') {
options.inputDir = processInputOption(options.inputDir)
}
if (options.auxInputDir && typeof options.auxInputDir === 'string') {
options.auxInputDir = processInputOption(options.auxInputDir)
}
if (options.libraryPackages && typeof options.libraryPackages === 'string') {
options.libraryPackages = processInputOption(options.libraryPackages)
}
const inputDirs: string[] = options.inputDir || []
const auxInputDirs: string[] = options.auxInputDir || []
const inputFiles: string[] = options.inputFiles || []
const auxInputFiles: string[] = options.auxInputFiles || []
const libraryPackages: string[] = options.libraryPackages || []
let baseDirs = options.baseDir ? processInputOption(options.baseDir) : inputDirs
if (!baseDirs.length && inputFiles.length) {
baseDirs = [...(new Set<string>(options.inputFiles.map((it:string) => path.dirname(it))).values())]
}
if (!baseDirs.length)
throw new Error("Check your --base-dir parameter, value is missing")
return {
baseDirs,
inputDirs,
auxInputDirs,
inputFiles,
auxInputFiles,
libraryPackages
}
}
function validatePaths(paths: string[], type: 'file' | 'dir'): void {
paths.forEach(pathItem => {
if (!fs.existsSync(pathItem)) {
console.error(`Input ${type} does not exist: ${pathItem}`)
process.exit(1)
} else {
console.log(`Input ${type} exists: ${pathItem}`)
}
})
}
function main() {
console.log(`IDLize Linter version ${findVersion()}`)
patchDefaultConfiguration({
rootComponents: [
"Root",
"ComponentRoot",
"CommonMethod",
"SecurityComponentMethod",
"CommonTransition",
"CalendarAttribute",
"ContainerSpanAttribute",
"CustomLayoutRoot"
],
standaloneComponents: [
"TextPickerDialog",
"TimePickerDialog",
"AlertDialog",
"CanvasPattern"
]
})
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).filter(it => it.endsWith('.d.ts'))
const allEntries = new Array<LinterMessage[]>()
generate(
baseDirs,
[...inputDirs, ...auxInputDirs],
dtsInputFiles,
[],
options.outputDir,
path.resolve(__dirname, "..", "stdlib.d.ts"),
(sourceFile, program, compilerHost) => new LinterVisitor(sourceFile, program, compilerHost),
{
compilerOptions: defaultCompilerOptions,
recursive: options.recursive,
onSingleFile: (entries: LinterMessage[]) => allEntries.push(entries),
onBegin: () => { },
onEnd: (outputDir) => {
const outFile = options.outputDir ? path.join(outputDir, "linter.txt") : undefined
const histogramFile = options.outputDir ? path.join(outputDir, "histogram.txt") : undefined
let [generated, exitCode, histogram] = toLinterString(allEntries, options.suppressErrors, options.whitelist)
console.log(histogram)
if (!outFile || options.verbose) console.log(generated)
if (outFile) fs.writeFileSync(outFile, generated)
if (histogramFile) fs.writeFileSync(histogramFile, histogram)
process.exit(exitCode)
}
}
)
}
main()