Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Support for *.mts/*.cts files #6909

Merged
merged 9 commits into from Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindings/swc_cli/src/commands/compile.rs
Expand Up @@ -121,7 +121,7 @@ static COMPILER: Lazy<Arc<Compiler>> = Lazy::new(|| {
});

/// List of file extensions supported by default.
static DEFAULT_EXTENSIONS: &[&str] = &["js", "jsx", "es6", "es", "mjs", "ts", "tsx"];
static DEFAULT_EXTENSIONS: &[&str] = &["js", "jsx", "es6", "es", "mjs", "ts", "tsx", "cts", "mts"];

/// Infer list of files to be transformed from cli arguments.
/// If given input is a directory, it'll traverse it and collect all supported
Expand Down
14 changes: 14 additions & 0 deletions crates/swc/src/config/mod.rs
Expand Up @@ -768,6 +768,20 @@ impl Default for Rc {
},
..Default::default()
},
Config {
env: None,
test: Some(FileMatcher::Regex("\\.(cts|mts)$".into())),
exclude: None,
jsc: JscConfig {
syntax: Some(Syntax::Typescript(TsConfig {
tsx: false,
disallow_ambiguous_jsx_like: true,
..Default::default()
})),
..Default::default()
},
..Default::default()
},
Config {
env: None,
test: Some(FileMatcher::Regex("\\.ts$".into())),
Expand Down
1 change: 1 addition & 0 deletions crates/swc/tests/rust_api.rs
Expand Up @@ -136,6 +136,7 @@ fn shopify_2_same_opt() {
decorators: false,
dts: false,
no_early_errors: false,
disallow_ambiguous_jsx_like: false,
})),
transform: None.into(),
external_helpers: false.into(),
Expand Down
1 change: 1 addition & 0 deletions crates/swc/tests/tsc.rs
Expand Up @@ -378,6 +378,7 @@ fn matrix(input: &Path) -> Vec<TestUnitData> {
decorators,
dts: false,
no_early_errors: false,
disallow_ambiguous_jsx_like: false,
})),
external_helpers: true.into(),
target: Some(target),
Expand Down
1 change: 1 addition & 0 deletions crates/swc_ecma_dep_graph/src/lib.rs
Expand Up @@ -394,6 +394,7 @@ mod tests {
tsx: file_name.contains("tsx"),
decorators: true,
no_early_errors: true,
disallow_ambiguous_jsx_like: false,
}),
EsVersion::Es2015,
(&*fm).into(),
Expand Down
11 changes: 11 additions & 0 deletions crates/swc_ecma_parser/src/error.rs
Expand Up @@ -279,6 +279,9 @@ pub enum SyntaxError {
span: Span,
note: &'static str,
},

ReservedTypeAssertion,
ReservedArrowTypeParam,
}

impl SyntaxError {
Expand Down Expand Up @@ -711,6 +714,14 @@ impl SyntaxError {
format!("Unexpected token. Did you mean {}?", did_you_mean).into()
}
SyntaxError::WithLabel { inner, .. } => inner.error.1.msg(),
SyntaxError::ReservedTypeAssertion => "This syntax is reserved in files with the .mts \
or .cts extension. Use an `as` expression \
instead."
.into(),
SyntaxError::ReservedArrowTypeParam => "This syntax is reserved in files with the \
.mts or .cts extension. Add a trailing comma, \
as in `<T,>() => ...`."
.into(),
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions crates/swc_ecma_parser/src/lib.rs
Expand Up @@ -264,6 +264,13 @@ impl Syntax {
Syntax::Es(..) => true,
}
}

pub fn disallow_ambiguous_jsx_like(self) -> bool {
match self {
Syntax::Typescript(t) => t.disallow_ambiguous_jsx_like,
_ => false,
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
Expand All @@ -281,6 +288,14 @@ pub struct TsConfig {

#[serde(skip, default)]
pub no_early_errors: bool,

/// babel: `disallowAmbiguousJSXLike`
/// Even when JSX parsing is not enabled, this option disallows using syntax
/// that would be ambiguous with JSX (`<X> y` type assertions and
/// `<X>()=>{}` type arguments)
/// see: https://babeljs.io/docs/en/babel-plugin-transform-typescript#disallowambiguousjsxlike
#[serde(skip, default)]
pub disallow_ambiguous_jsx_like: bool,
kdy1 marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
Expand Down
4 changes: 4 additions & 0 deletions crates/swc_ecma_parser/src/parser/expr.rs
Expand Up @@ -82,6 +82,7 @@ impl<I: Tokens> Parser<I> {
#[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
fn parse_assignment_expr_base(&mut self) -> PResult<Box<Expr>> {
trace_cur!(self, parse_assignment_expr_base);
let start = self.input.cur_span();

if self.input.syntax().typescript()
&& (is_one_of!(self, '<', JSXTagStart))
Expand Down Expand Up @@ -120,6 +121,9 @@ impl<I: Tokens> Parser<I> {
Ok(Some(arrow))
});
if let Some(res) = res {
if self.input.syntax().disallow_ambiguous_jsx_like() {
self.emit_err(start, SyntaxError::ReservedArrowTypeParam);
}
return Ok(res);
}
}
Expand Down
5 changes: 5 additions & 0 deletions crates/swc_ecma_parser/src/parser/typescript.rs
Expand Up @@ -994,6 +994,10 @@ impl<I: Tokens> Parser<I> {
pub(super) fn parse_ts_type_assertion(&mut self, start: BytePos) -> PResult<TsTypeAssertion> {
debug_assert!(self.input.syntax().typescript());

if self.input.syntax().disallow_ambiguous_jsx_like() {
self.emit_err(span!(self, start), SyntaxError::ReservedTypeAssertion);
}

// Not actually necessary to set state.inType because we never reach here if JSX
// plugin is enabled, but need `tsInType` to satisfy the assertion in
// `tsParseType`.
Expand Down Expand Up @@ -2624,6 +2628,7 @@ impl<I: Tokens> Parser<I> {
let res = if is_one_of!(self, '<', JSXTagStart) {
self.try_parse_ts(|p| {
let type_params = p.parse_ts_type_params(false, false)?;

// Don't use overloaded parseFunctionParams which would look for "<" again.
expect!(p, '(');
let params: Vec<Pat> = p
Expand Down
@@ -0,0 +1 @@
<T>x;
@@ -0,0 +1,6 @@

x This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.
,-[$DIR/tests/typescript-errors/disallow-ambiguous-jsx-like/cts-case1/input.cts:1:1]
1 | <T>x;
: ^
`----
@@ -0,0 +1 @@
<T>() => 1;
@@ -0,0 +1,6 @@

x This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma, as in `<T,>() => ...`.
,-[$DIR/tests/typescript-errors/disallow-ambiguous-jsx-like/cts-case2/input.cts:1:1]
1 | <T>() => 1;
: ^
`----
@@ -0,0 +1 @@
<T>x;
@@ -0,0 +1,6 @@

x This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.
,-[$DIR/tests/typescript-errors/disallow-ambiguous-jsx-like/mts-case1/input.mts:1:1]
1 | <T>x;
: ^
`----
@@ -0,0 +1 @@
<T>(x) => 1;
@@ -0,0 +1,6 @@

x This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma, as in `<T,>() => ...`.
,-[$DIR/tests/typescript-errors/disallow-ambiguous-jsx-like/mts-case2/input.mts:1:1]
1 | <T>(x) => 1;
: ^
`----
5 changes: 5 additions & 0 deletions crates/swc_ecma_parser/tests/typescript.rs
Expand Up @@ -67,6 +67,8 @@ fn shifted(file: PathBuf) {
}

#[testing::fixture("tests/typescript/**/*.ts")]
#[testing::fixture("tests/typescript/**/*.mts")]
#[testing::fixture("tests/typescript/**/*.cts")]
#[testing::fixture("tests/typescript/**/*.tsx")]
fn spec(file: PathBuf) {
let output = file.parent().unwrap().join(format!(
Expand Down Expand Up @@ -240,6 +242,7 @@ where
tsx: fname.contains("tsx"),
decorators: true,
no_early_errors,
disallow_ambiguous_jsx_like: fname.contains("cts") || fname.contains("mts"),
..Default::default()
}),
EsVersion::Es2015,
Expand All @@ -264,6 +267,8 @@ where
}

#[testing::fixture("tests/typescript-errors/**/*.ts")]
#[testing::fixture("tests/typescript-errors/**/*.mts")]
#[testing::fixture("tests/typescript-errors/**/*.cts")]
#[testing::fixture("tests/typescript-errors/**/*.tsx")]
fn errors(file: PathBuf) {
let file_name = file.display().to_string();
Expand Down
3 changes: 3 additions & 0 deletions crates/swc_ecma_parser/tests/typescript/cts/1.cts
@@ -0,0 +1,3 @@
const path = require('path');

console.log("hello, world!");
137 changes: 137 additions & 0 deletions crates/swc_ecma_parser/tests/typescript/cts/1.cts.json
@@ -0,0 +1,137 @@
{
"type": "Script",
"span": {
"start": 1,
"end": 61,
"ctxt": 0
},
"body": [
{
"type": "VariableDeclaration",
"span": {
"start": 1,
"end": 30,
"ctxt": 0
},
"kind": "const",
"declare": false,
"declarations": [
{
"type": "VariableDeclarator",
"span": {
"start": 7,
"end": 29,
"ctxt": 0
},
"id": {
"type": "Identifier",
"span": {
"start": 7,
"end": 11,
"ctxt": 0
},
"value": "path",
"optional": false,
"typeAnnotation": null
},
"init": {
"type": "CallExpression",
"span": {
"start": 14,
"end": 29,
"ctxt": 0
},
"callee": {
"type": "Identifier",
"span": {
"start": 14,
"end": 21,
"ctxt": 0
},
"value": "require",
"optional": false
},
"arguments": [
{
"spread": null,
"expression": {
"type": "StringLiteral",
"span": {
"start": 22,
"end": 28,
"ctxt": 0
},
"value": "path",
"raw": "'path'"
}
}
],
"typeArguments": null
},
"definite": false
}
]
},
{
"type": "ExpressionStatement",
"span": {
"start": 32,
"end": 61,
"ctxt": 0
},
"expression": {
"type": "CallExpression",
"span": {
"start": 32,
"end": 60,
"ctxt": 0
},
"callee": {
"type": "MemberExpression",
"span": {
"start": 32,
"end": 43,
"ctxt": 0
},
"object": {
"type": "Identifier",
"span": {
"start": 32,
"end": 39,
"ctxt": 0
},
"value": "console",
"optional": false
},
"property": {
"type": "Identifier",
"span": {
"start": 40,
"end": 43,
"ctxt": 0
},
"value": "log",
"optional": false
}
},
"arguments": [
{
"spread": null,
"expression": {
"type": "StringLiteral",
"span": {
"start": 44,
"end": 59,
"ctxt": 0
},
"value": "hello, world!",
"raw": "\"hello, world!\""
}
}
],
"typeArguments": null
}
}
],
"interpreter": null
}
@@ -0,0 +1,2 @@
<T,>() => 1;
<T,>(x) => 1;