swc-project/swc · error

TsParameterProperty should be removed by typescript::strip p

Error message

TsParameterProperty should be removed by typescript::strip pass

What it means

The shared compat helper (behind the handle-functions macro used by es2015+ passes) rewrites function-like nodes and expects constructor parameters to already be plain Params. TypeScript parameter properties (`constructor(private x: number)`) are removed by the typescript strip pass; if it has not run, ParamOrTsParamProp::TsParamProp reaches unreachable!().

Source

Thrown at crates/swc_ecma_compat_common/src/macros.rs:120

        }

        fn visit_mut_constructor(&mut self, f: &mut Constructor) {
            if f.body.is_none() {
                return;
            }

            #[cfg(debug_assertions)]
            tracing::trace!("visit_mut_constructor(parmas.len() = {})", f.params.len());

            f.visit_mut_children_with(self);

            let mut params = f
                .params
                .take()
                .into_iter()
                .map(|pat| match pat {
                    ParamOrTsParamProp::Param(p) => p,
                    _ => unreachable!(
                        "TsParameterProperty should be removed by typescript::strip pass"
                    ),
                })
                .collect();

            let body = f.body.as_mut().unwrap();
            let (params, stmts) = self.visit_mut_fn_like(&mut params, &mut body.stmts);

            #[cfg(debug_assertions)]
            tracing::trace!(
                "visit_mut_constructor(parmas.len() = {}, after)",
                params.len()
            );

            f.params = params.into_iter().map(ParamOrTsParamProp::Param).collect();
            body.stmts = stmts;
        }
    };

View on GitHub (pinned to 5176682b65)

Solutions

  1. Use the official pass ordering: run the TypeScript transforms (typescript::strip or the full target chain via swc core's build/TransformOptions) before any es compat pass
  2. If assembling passes manually, prepend swc_ecma_transforms_typescript::strip to the chain
  3. Remove parameter properties from the source: declare fields explicitly and assign them in the constructor

Example fix

// before
class Service {
  constructor(private logger: Logger) {}
}

// after
class Service {
  private logger: Logger;
  constructor(logger: Logger) { this.logger = logger; }
}
Defensive patterns

Strategy: validation

Validate before calling

// Detect TS constructor parameter properties before running raw compat passes
let param_prop = regex::Regex::new(
    r#"constructor\s*\([^)]*\b(?:public|private|protected|readonly|override)\s+[A-Za-z_$]"#,
).unwrap();
if param_prop.is_match(&ts_source) {
    return Err("run typescript::strip before es compat passes".into());
}

Try / catch

if let Err(p) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| apply_compat(&mut program))) {
    if panic_message(&p).contains("TsParameterProperty") {
        // fix: prepend swc_ecma_transforms_typescript::strip to the chain and retry
    } else { std::panic::resume_unwind(p); }
}

Prevention

When it happens

Trigger: Running an individual es2015 compat pass (classes, arrow, computed-props, etc. — anything built on these macros) directly on TypeScript source using constructor parameter properties, without typescript::strip having run first.

Common situations: Custom transform pipelines that cherry-pick compat passes instead of using swc's preset ordering; plugins applied to raw .ts input; migrating hand-rolled pass chains to newer swc versions.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/5f4dc4f8953d6c08. Report an issue: GitHub.