// The `mtsc` option layer: a superset of `tsc` / `tsgo`'s command line.
//
// "Superset" is a claim with three tiers behind it, and the third is the
// one that makes the first two safe to make:
//
// HONOURED — the option maps onto behaviour mtsc really has.
// ACCEPTED — mtsc understands the option, has no behaviour for it, and
// SAYS SO once per run. A pipeline built around `tsc` passes
// `--target es2020 --module esnext --strict`; hard-failing
// there would make the superset claim useless, and silently
// accepting it would be a wrong answer with no symptom.
// REJECTED — not a `tsc` option and not an mtsc option. Exit non-zero.
//
// The middle tier is the whole design. Every other compiler that claims
// `tsc` compatibility picks one of the two extremes, and both are worse:
// rejecting means the flag vocabulary can never be shared, accepting
// silently means `--strict false` looks obeyed while the checker runs
// strictly anyway.
//
// Option NAMES are matched case-insensitively with `-` and `_` removed,
// so `--noEmit`, `--no-emit`, `--noemit` and `--NoEmit` are one option.
// That is a strict superset of what `tsc` itself does (it lowercases the
// name and stops there), and it is what lets mtsc's own kebab-case
// history (`--no-check`, `--jsx-import-source`) and `tsc`'s camelCase
// spelling (`--noCheck`, `--jsxImportSource`) name the same flag without
// two tables that can disagree.
///|
/// `tsc`'s exit code for "diagnostics were reported and no output was
/// written", so a caller's `if mtsc ...; then` keeps meaning what it
/// meant under `tsc`.
///
/// The full set `tsc` uses is 0 success, 1 diagnostics with outputs
/// skipped, 2 diagnostics with outputs generated anyway. Only 0 and 1
/// are reachable here — a failing check stops the emit, and `--noCheck`
/// skips the check rather than demoting it, so there is no path that
/// reports a diagnostic and still writes — and a constant for a code
/// nothing can return would be a claim about behaviour mtsc does not
/// have.
///
/// `tsc` reports a bad command line as a diagnostic and exits 1 as well,
/// which is why there is no separate usage code. mtsc used to exit 0 on
/// an unknown option, so a typo in a build script was invisible.
const MTSC_EXIT_DIAGNOSTICS : Int = 1
///|
/// Canonical form of an option name: leading dashes stripped, ASCII
/// lowercased, `-` and `_` removed.
///
/// The dash removal is deliberate and is what makes one table serve both
/// spellings. It can only merge two names that differ solely in
/// separators and case, and no two distinct options in either vocabulary
/// do — `--out` / `--outDir` stay apart because they differ in letters,
/// not in punctuation.
fn mtsc_normalize_option(raw : String) -> String {
let mut start = 0
while start < raw.length() && raw[start] == '-' {
start = start + 1
}
let out = StringBuilder()
for i in start..= 'A'.to_int() && code <= 'Z'.to_int() {
code + 32
} else {
code
}
out.write_char(lowered.unsafe_to_char())
}
out.to_string()
}
///|
/// Split `--name=value` into its halves. Returns `None` when there is no
/// `=`, so the caller falls back to reading the next argv element.
///
/// `tsc` takes only the two-token form; accepting `=` as well costs
/// nothing and is what every other bundler's users expect.
fn mtsc_split_inline_value(arg : String) -> (String, String)? {
match arg.find("=") {
Some(i) => Some((arg[0:i].to_owned(), arg[i + 1:].to_owned()))
None => None
}
}
///|
/// Options that consume a value, by canonical name. Kept as one
/// predicate rather than per-arm `i + 1` arithmetic so a new value-taking
/// option cannot be added without deciding this question.
fn mtsc_option_takes_value(name : String) -> Bool {
match name {
// mtsc's own
"out"
| "o"
| "external"
| "disablephase"
| "jsxruntime"
| "jsximportsource"
| "jsxfactory"
| "jsxfragmentfactory" => true
// tsc's, honoured
"outfile" | "outdir" | "project" | "p" | "jsx" | "declarationdir" => true
// tsc's, accepted with no effect — they still have to consume their
// value, or it is read as an input file. `mtsc --target es2020 a.ts`
// would otherwise compile a file called `es2020`.
"target"
| "t"
| "module"
| "m"
| "moduleresolution"
| "lib"
| "rootdir"
| "rootdirs"
| "baseurl"
| "paths"
| "types"
| "typeroots"
| "tsbuildinfofile"
| "newline"
| "locale"
| "charset"
| "maxnodemodulejsdepth"
| "reactnamespace"
| "generatetrace"
| "moduledetection"
| "importsnotusedasvalues"
| "jsxfrag"
| "plugins"
| "watchfile"
| "watchdirectory"
| "fallbackpolling"
| "excludedirectories"
| "excludefiles" => true
_ => false
}
}
///|
/// A `tsc` option mtsc understands and has no behaviour for. Listed
/// rather than inferred from "everything left over", because the leftover
/// set is exactly where a typo has to land.
///
/// Several of these are honest near-misses worth naming. `--strict` and
/// the strict family cannot be honoured because `@mtsc_core.collect_type_issues`
/// takes a source and a JSX flag and nothing else — strictness is read
/// per-file from the module's own directives — so wiring the flag through
/// means changing that API rather than this table. `--target` / `--module`
/// cannot be honoured because mtsc emits one level (ESM, current syntax)
/// and does not downlevel.
fn mtsc_option_accepted_no_effect(name : String) -> Bool {
match name {
"target"
| "t"
| "module"
| "m"
| "moduleresolution"
| "lib"
| "strict"
| "noimplicitany"
| "strictnullchecks"
| "strictfunctiontypes"
| "strictbindcallapply"
| "strictpropertyinitialization"
| "strictbuiltiniteratorreturn"
| "noimplicitthis"
| "usedefineforclassfields"
| "alwaysstrict"
| "nounusedlocals"
| "nounusedparameters"
| "exactoptionalpropertytypes"
| "noimplicitreturns"
| "nofallthroughcasesinswitch"
| "nouncheckedindexedaccess"
| "noimplicitoverride"
| "nopropertyaccessfromindexsignature"
| "allowunusedlabels"
| "allowunreachablecode"
| "incremental"
| "composite"
| "tsbuildinfofile"
| "removecomments"
| "importhelpers"
| "downleveliteration"
| "esmoduleinterop"
| "allowjs"
| "checkjs"
| "skiplibcheck"
| "skipdefaultlibcheck"
| "rootdir"
| "rootdirs"
| "baseurl"
| "paths"
| "types"
| "typeroots"
| "allowsyntheticdefaultimports"
| "allowumdglobalaccess"
| "allowarbitraryextensions"
| "allowimportingtsextensions"
| "forceconsistentcasinginfilenames"
| "isolatedmodules"
| "isolateddeclarations"
| "verbatimmodulesyntax"
| "resolvejsonmodule"
| "resolvepackagejsonexports"
| "resolvepackagejsonimports"
| "newline"
| "preserveconstenums"
| "preservesymlinks"
| "preservewatchoutput"
| "declarationmap"
| "declarationdir"
| "inlinesourcemap"
| "inlinesources"
| "sourceroot"
| "maproot"
| "diagnostics"
| "extendeddiagnostics"
| "generatetrace"
| "noerrortruncation"
| "explainfiles"
| "traceresolution"
| "locale"
| "charset"
| "experimentaldecorators"
| "emitdecoratormetadata"
| "emitbom"
| "stripinternal"
| "notemitonerror"
| "noemitonerror"
| "noemithelpers"
| "nolib"
| "noresolve"
| "suppressexcesspropertyerrors"
| "suppressimplicitanyindexerrors"
| "keyofstringsonly"
| "maxnodemodulejsdepth"
| "reactnamespace"
| "jsxfrag"
| "moduledetection"
| "importsnotusedasvalues"
| "plugins"
| "assumechangesonlyaffectdirectdependencies"
| "disablesizelimit"
| "disablesourceofprojectreferenceredirect"
| "disablesolutionsearching"
| "disablereferencedprojectload"
| "erasablesyntaxonly"
| "libreplacement"
| "watchfile"
| "watchdirectory"
| "fallbackpolling"
| "synchronouswatchdirectory"
| "excludedirectories"
| "excludefiles" => true
_ => false
}
}
///|
priv struct MtscOptions {
// Every input the command line named. `tsc` takes a list; mtsc's
// pipeline is single-entry, so a list is compiled entry by entry.
inputs : Array[String]
mut out : String?
// `--outDir`. With more than one input, or with `--outDir` and no
// `--out`, this is where each entry's JavaScript lands.
mut out_dir : String?
// `-p` / `--project`: a directory holding a `tsconfig.json`, or the
// config path itself. A bare `mtsc` with no inputs looks for
// `tsconfig.json` in the working directory, the way `tsc` does.
mut project : String?
mut bundle : Bool
mut treeshake : Bool
mut fold : Bool
mut minify : Bool
// Library-mode opt-in. When `true`, every property name that
// appears in an interface / type alias / class declaration is
// added to the mangler's reserved set so downstream consumers
// can still type-check against the emitted bundle. Off by
// default because plain `--bundle` is self-contained.
mut reserve_typed_props : Bool
// Selective library mode: reserve only the property names
// reachable from the ENTRY module's `export` declarations.
// Internal helper types declared in the entry (or elsewhere)
// are still fair game to mangle. External IO surface
// (`console`, `fetch`, `JSON`, etc.) stays reserved via the
// built-in globals list in `mangle_safety.mbt`. Off by default;
// takes precedence over `--reserve-typed-props` when both are set.
mut reserve_entry_exports : Bool
// Emit a companion `.d.ts` next to the JS output. Contains only
// the entry module's exported type surface — internal helper
// types are omitted. Requires `--bundle`. `tsc`'s `--declaration`
// / `-d` is the same request and sets this.
mut dts : Bool
// `--emitDeclarationOnly`: write the `.d.ts` and skip the JavaScript.
mut dts_only : Bool
// `--noEmit`: type-check and write nothing. This is what `tsc` does
// by far the most often in CI, and mtsc had no spelling for it — the
// only way to check without emitting was the separate `tscheck`
// binary, whose single-file semantics are a different question.
mut no_emit : Bool
// Print a warning when a getter's body contains a direct side
// effect (assignment, call to an impure function, `++` / `--`,
// etc). Getters in JS are conventionally pure; an impure one
// is almost always a bug. Off by default to keep output quiet.
mut warn_impure_getters : Bool
// Report reflection in the input that no optimization can see
// through — `Reflect.ownKeys` and friends enumerate NON-enumerable
// properties, which is exactly what a class prototype method is, so
// one call anywhere keeps every method of every class in the bundle.
//
// On by default, unlike `--warn-impure-getters`, because the author
// is the only one who can act on it and should not have to know a
// flag exists to find out. Suppressed when the JS itself is going to
// stdout: there the diagnostic would land inside the program.
// `--warn-reflection` forces it on even then, `--no-warn-reflection`
// off.
mut warn_reflection : Bool?
// Bare specifiers the user wants left as `import … from ""`
// in the emitted bundle. Anything not on this list is resolved
// through node_modules / tsconfig paths and inlined when found.
externals : Map[String, Bool]
mut mangle : Bool
mut mangle_properties : Bool
// `--explain-mangle`: print why each minify decision went the way it
// did instead of only doing it. Implies the mangle passes, since a
// decision has to be made before it can be explained.
mut explain_mangle : Bool
// `--verify`: re-parse the emitted bundle and report any name it
// reads that nothing declares. See `docs/type-aware-dce.md` (E1).
mut verify : Bool
// `--disable-phase `: run without a named pipeline phase, so a
// size delta can be attributed to it. `--timing` names every phase.
disabled_phases : Map[String, Bool]
mut mangle_properties_shape_color : Bool
// `--no-check` / `tsc`'s `--noCheck`: skip type-checking. The gate
// exists because a type error usually means the program is wrong, but
// it also makes the whole pipeline unreachable for input that is not
// TypeScript in the first place — a published `.js` bundle, where an
// object literal grows properties at runtime and every such site reads
// as an error.
//
// It SKIPS rather than demotes. The checker is ~95% of a large
// compile (29.4 s of 30.6 s on `checker.ts`), and once the caller has
// said diagnostics must not stop the emit, running it buys output
// nobody acts on. `mtsc check` is the way to see diagnostics without
// an abort.
mut no_check : Bool
// `--timing`: report wall time per pipeline phase. The compile is
// superlinear in input size and a twenty-pass pipeline gives a guess
// nothing to stand on, so the passes report instead.
mut timing : Bool
mut sourcemap : Bool
mut jsx_runtime : String?
mut jsx_import_source : String?
mut jsx_factory : String?
mut jsx_fragment_factory : String?
mut jsx_dev : Bool
mut help : Bool
mut version : Bool
// `--watch` / `-w`: recompile when an input changes.
mut watch : Bool
// `--init`: write a starter `tsconfig.json`.
mut init : Bool
// `--showConfig`: print the resolved configuration and exit.
mut show_config : Bool
// `--listFiles` / `--listFilesOnly`: name every file in the program.
mut list_files : Bool
mut list_files_only : Bool
// `tsc` options mtsc parsed and has no behaviour for, in the order
// they were given. Reported once so an inherited `tsc` command line
// does not quietly mean something narrower than it says.
ignored : Array[String]
}
///|
fn mtsc_options_new() -> MtscOptions {
{
inputs: [],
out: None,
out_dir: None,
project: None,
bundle: false,
treeshake: false,
fold: false,
minify: false,
reserve_typed_props: false,
reserve_entry_exports: false,
dts: false,
dts_only: false,
no_emit: false,
warn_impure_getters: false,
warn_reflection: None,
externals: {},
mangle: false,
mangle_properties: false,
explain_mangle: false,
verify: false,
disabled_phases: {},
mangle_properties_shape_color: false,
no_check: false,
timing: false,
sourcemap: false,
jsx_runtime: None,
jsx_import_source: None,
jsx_factory: None,
jsx_fragment_factory: None,
jsx_dev: false,
help: false,
version: false,
watch: false,
init: false,
show_config: false,
list_files: false,
list_files_only: false,
ignored: [],
}
}
///|
/// Outcome of reading a boolean option's optional value.
///
/// `tsc` lets every boolean take an explicit `true` / `false`
/// (`--strict false`), and the token is optional. So the value can only
/// be consumed when it IS one of those two words — consuming
/// unconditionally would eat the input file in `mtsc --noEmit a.ts`.
fn mtsc_read_bool_value(argv : Array[String], next : Int) -> (Bool, Bool) {
if next < argv.length() {
match argv[next] {
"true" => return (true, true)
"false" => return (false, true)
_ => ()
}
}
(true, false)
}
///|
/// Map `tsc`'s `--jsx` setting onto mtsc's JSX runtime selection.
///
/// `preserve` and `react-native` leave JSX in the output, which this
/// pipeline cannot do — it always lowers — so they map to the automatic
/// runtime and are reported rather than honoured. That is the same
/// decision `mtsc_build_jsx_config` already makes when it reads the
/// setting out of tsconfig; the difference here is that a command line
/// asked for it explicitly, so it gets a line of output.
fn mtsc_apply_jsx_setting(opts : MtscOptions, value : String) -> Unit {
match value {
"react" => opts.jsx_runtime = Some("classic")
"react-jsx" => opts.jsx_runtime = Some("automatic")
"react-jsxdev" => {
opts.jsx_runtime = Some("automatic")
opts.jsx_dev = true
}
"preserve" | "react-native" => {
opts.jsx_runtime = Some("automatic")
opts.ignored.push("--jsx \{value} (mtsc always lowers JSX)")
}
_ =>
opts.ignored.push(
"--jsx \{value} (unrecognized; using the automatic runtime)",
)
}
}
///|
/// Parse the command line into `MtscOptions`.
///
/// `start` is the index of the first option, so the caller can hand this
/// an argv whose leading elements it has already consumed.
fn mtsc_parse_args(
argv : Array[String],
start : Int,
) -> Result[MtscOptions, String] {
let opts = mtsc_options_new()
let mut i = start
while i < argv.length() {
let raw = argv[i]
// A bare `-` or anything not starting with `-` is an input path.
if !raw.has_prefix("-") || raw == "-" {
opts.inputs.push(raw)
i = i + 1
continue
}
// `--name=value` is split before normalization so the name half
// normalizes on its own.
let (name_token, inline_value) = match mtsc_split_inline_value(raw) {
Some((n, v)) => (n, Some(v))
None => (raw, None)
}
let name = mtsc_normalize_option(name_token)
// Resolve this option's value, from `=value` or the next token.
let mut consumed_next = false
let value : String? = match inline_value {
Some(v) => Some(v)
None =>
if mtsc_option_takes_value(name) {
if i + 1 >= argv.length() {
return Err("missing value for \{raw}")
}
consumed_next = true
Some(argv[i + 1])
} else {
None
}
}
// One accessor for the resolved value, so an arm cannot read a value
// this function did not decide to consume.
fn need_value() -> Result[String, String] {
match value {
Some(v) => Ok(v)
None => Err("missing value for \{raw}")
}
}
match name {
// ----------------------------------------------------- help / info
"help" | "h" | "all" | "?" => opts.help = true
"version" | "v" => opts.version = true
"init" => opts.init = true
"showconfig" => opts.show_config = true
"listfiles" => opts.list_files = true
"listfilesonly" => {
opts.list_files = true
opts.list_files_only = true
}
// `--pretty` controls colour, and mtsc emits none either way, so
// there is nothing to honour and nothing lost.
"pretty" | "nocolors" | "nocolor" => ()
// ------------------------------------------------------------ paths
"out" | "o" | "outfile" =>
match need_value() {
Ok(v) => opts.out = Some(v)
Err(e) => return Err(e)
}
"outdir" =>
match need_value() {
Ok(v) => opts.out_dir = Some(v)
Err(e) => return Err(e)
}
"project" | "p" =>
match need_value() {
Ok(v) => opts.project = Some(v)
Err(e) => return Err(e)
}
// ------------------------------------------------- emit selection
"noemit" => {
let (v, used) = mtsc_read_bool_value(argv, i + 1)
opts.no_emit = v
if used && inline_value is None {
consumed_next = true
}
}
"declaration" | "d" => {
let (v, used) = mtsc_read_bool_value(argv, i + 1)
opts.dts = v
if v {
opts.bundle = true
}
if used && inline_value is None {
consumed_next = true
}
}
"emitdeclarationonly" => {
opts.dts = true
opts.dts_only = true
opts.bundle = true
}
"dts" => {
opts.dts = true
opts.bundle = true
}
"sourcemap" => {
let (v, used) = mtsc_read_bool_value(argv, i + 1)
opts.sourcemap = v
if used && inline_value is None {
consumed_next = true
}
}
// ----------------------------------------------------- type-checking
"nocheck" => {
let (v, used) = mtsc_read_bool_value(argv, i + 1)
opts.no_check = v
if used && inline_value is None {
consumed_next = true
}
}
// ------------------------------------------------------------ watch
"watch" | "w" => opts.watch = true
// `--build` needs project references and an up-to-date check, and
// mtsc has neither. Saying so beats accepting it and compiling
// something else: `tsc -b` on a solution file is not a request to
// compile that file.
"build" | "b" =>
return Err(
"--build is not supported (mtsc has no project references); compile each project with -p",
)
// -------------------------------------------------------------- JSX
"jsx" =>
match need_value() {
Ok(v) => mtsc_apply_jsx_setting(opts, v)
Err(e) => return Err(e)
}
"jsxruntime" =>
match need_value() {
Ok(v) => opts.jsx_runtime = Some(v)
Err(e) => return Err(e)
}
"jsximportsource" =>
match need_value() {
Ok(v) => opts.jsx_import_source = Some(v)
Err(e) => return Err(e)
}
"jsxfactory" =>
match need_value() {
Ok(v) => opts.jsx_factory = Some(v)
Err(e) => return Err(e)
}
"jsxfragmentfactory" =>
match need_value() {
Ok(v) => opts.jsx_fragment_factory = Some(v)
Err(e) => return Err(e)
}
"jsxdev" => opts.jsx_dev = true
// --------------------------------------------- mtsc's own pipeline
"bundle" => opts.bundle = true
"treeshake" => {
opts.treeshake = true
opts.bundle = true
}
"minify" => opts.minify = true
"fold" => {
opts.fold = true
opts.bundle = true
}
"reservetypedprops" => opts.reserve_typed_props = true
"reserveentryexports" => opts.reserve_entry_exports = true
"warnimpuregetters" => opts.warn_impure_getters = true
"warnreflection" => opts.warn_reflection = Some(true)
"nowarnreflection" => opts.warn_reflection = Some(false)
"external" =>
match need_value() {
Ok(v) =>
for part in v.split(",") {
let spec = part.to_owned().trim().to_owned()
if spec != "" {
opts.externals[spec] = true
}
}
Err(e) => return Err(e)
}
"mangle" => opts.mangle = true
// Property mangling implies `--bundle`, the way `--treeshake` and
// `--fold` already do — and for a stronger reason than they have.
// The escape analysis (both doors, the wildcard, the whole safety
// story) lives in `bundle_modules`. The single-file path has none
// of it, so `--mangle-properties` there renamed whatever it liked,
// including a CJS module's `exports.foo` and the keys of an
// exported object. A flag whose only safe form is one path should
// select that path rather than trust the user to.
"mangleproperties" => {
opts.mangle = true
opts.mangle_properties = true
opts.bundle = true
}
"explainmangle" => {
opts.mangle = true
opts.mangle_properties = true
opts.explain_mangle = true
opts.bundle = true
}
"manglepropertiesshapecolor" => {
opts.mangle = true
opts.mangle_properties = true
opts.mangle_properties_shape_color = true
opts.bundle = true
}
"verify" => opts.verify = true
"disablephase" =>
match need_value() {
Ok(v) =>
for part in v.split(",") {
let phase = part.to_owned().trim().to_owned()
if phase != "" {
opts.disabled_phases[phase] = true
}
}
Err(e) => return Err(e)
}
"timing" => {
opts.timing = true
opts.bundle = true
}
// ------------------------------------------- accepted, no effect
_ =>
if mtsc_option_accepted_no_effect(name) {
// A boolean in this tier may still carry `true` / `false`.
if inline_value is None && !mtsc_option_takes_value(name) {
let (_, used) = mtsc_read_bool_value(argv, i + 1)
if used {
consumed_next = true
}
}
opts.ignored.push(name_token)
} else {
return Err("unknown option: \{raw}")
}
}
i = i + (if consumed_next { 2 } else { 1 })
}
Ok(opts)
}
///|
fn mtsc_print_usage() -> Unit {
println("Usage: mtsc [options] [files...]")
println(" mtsc -p [options]")
println(" mtsc [args...]")
println("")
println(
"Type-check TypeScript / TSX and compile it to JavaScript. A superset of the",
)
println(
"`tsc` / `tsgo` command line: tsc option names are accepted, and the ones mtsc",
)
println("has no behaviour for are reported rather than silently ignored.")
println("")
println("Commands:")
println(
" check Report checker diagnostics for one file (no emit, always exits 0)",
)
println(
" conformance Score the checker against the pinned TypeScript conformance corpus",
)
println(
" bridge Generate a MoonBit bridge from TypeScript (`mtsc bridge --help`)",
)
println(
" pkg Generate a TypeScript package from MoonBit (`mtsc pkg --help`)",
)
println("")
println("tsc-compatible options:")
println(
" --project, -p Compile the project at (a directory or a tsconfig.json)",
)
println(" --noEmit Type-check and write nothing")
println(
" --noCheck Skip type-checking entirely (also spelled --no-check)",
)
println(
" --outFile, --out, -o Write JavaScript to (default: stdout)",
)
println(
" --outDir Write each input's JavaScript under ",
)
println(
" --declaration, -d Emit a companion .d.ts (also spelled --dts)",
)
println(" --emitDeclarationOnly Emit the .d.ts and skip the JavaScript")
println(" --sourceMap Emit .map alongside the JavaScript")
println(
" --jsx react | react-jsx | react-jsxdev (preserve is lowered)",
)
println(" --jsxImportSource Override the jsx-runtime import source")
println(" --jsxFactory Classic-runtime element factory")
println(" --jsxFragmentFactory Classic-runtime fragment factory")
println(" --watch, -w Recompile when an input changes")
println(" --init Write a starter tsconfig.json")
println(
" --showConfig Print the resolved configuration and exit",
)
println(" --listFiles Name every file in the program")
println(" --listFilesOnly Name every file in the program and stop")
println(" --version, -v Print the version")
println(" --help, -h Show this help")
println("")
println("mtsc options:")
println(
" --bundle Follow relative imports from the entry and emit one bundle",
)
println(
" --treeshake Drop top-level declarations unreached from any live stmt (implies --bundle)",
)
println(
" --minify Suppress indentation / newlines / optional whitespace",
)
println(
" --fold Constant-fold identity arithmetic and dead branches (implies --bundle)",
)
println(
" --external Keep this bare-specifier import external (repeatable; comma-separated also OK)",
)
println(
" --reserve-typed-props Reserve every property declared in an interface / type alias / class (library mode)",
)
println(
" --reserve-entry-exports Reserve only property names reachable from the entry's exports (library mode, selective)",
)
println(
" --warn-impure-getters Warn when a class getter body has a direct side effect (assign, call, mutation)",
)
println(" --mangle Rename non-exported local identifiers")
println(
" --mangle-properties Also rename non-reserved property keys (implies --bundle)",
)
println(
" --warn-reflection Report reflection that blocks an optimization (default on when -o is given)",
)
println(" --no-warn-reflection Silence the reflection report")
println(
" --explain-mangle Report why each minify decision went the way it did",
)
println(
" --mangle-properties-shape-color Use shape-coloring property rename (opt-in, experimental)",
)
println(
" --verify Re-parse the emitted bundle and report any name nothing declares",
)
println(
" --disable-phase Run without a named pipeline phase, to attribute bytes to it (repeatable; --timing lists the names)",
)
println(
" --timing Report wall time per pipeline phase (implies --bundle)",
)
println("")
println(
"Option names are matched ignoring case, `-` and `_`, so --noEmit, --no-emit and",
)
println("--noemit are one option.")
}
///|
/// Report the `tsc` options this run parsed and did not act on.
///
/// One line, after the option scan and before any work, so the reader
/// sees it next to the command they typed rather than buried under the
/// output. Silence here would be the failure mode this whole tier exists
/// to avoid.
fn mtsc_report_ignored(opts : MtscOptions) -> Unit {
if opts.ignored.length() == 0 {
return
}
let names : Array[String] = []
for name in opts.ignored {
names.push(name)
}
println(
"mtsc: accepted but not honoured: \{names.join(", ")} — mtsc emits current-syntax ESM and type-checks strictly, so these have no effect",
)
}