mirror of
https://github.com/swc-project/swc.git
synced 2024-10-04 04:07:18 +03:00
feat(typescript): Implement Isolated Declaration (#9086)
**Description:** This PR adds TypeScript Isolated Declarations implementation by forking Deno's implementation.
This commit is contained in:
parent
568b3b37a5
commit
a8551592b2
2
.github/workflows/CI.yml
vendored
2
.github/workflows/CI.yml
vendored
@ -587,7 +587,7 @@ jobs:
|
||||
- crate: testing_macros
|
||||
os: ubuntu-latest
|
||||
runner: ubuntu-latest
|
||||
- crate: xtask
|
||||
- crate: swc_typescript
|
||||
os: ubuntu-latest
|
||||
runner: ubuntu-latest
|
||||
steps:
|
||||
|
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -3561,9 +3561,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sourcemap"
|
||||
version = "8.0.0"
|
||||
version = "8.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf0b8c0d9d32f81aa0ab2b68ab634d9bbce287423606656fddb456ac8601aec3"
|
||||
checksum = "208d40b9e8cad9f93613778ea295ed8f3c2b1824217c6cfc7219d3f6f45b96d4"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bitvec",
|
||||
@ -5299,6 +5299,15 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "swc_typescript"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_codegen",
|
||||
"swc_ecma_parser",
|
||||
"testing",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_visit"
|
||||
|
@ -109,7 +109,7 @@ resolver = "2"
|
||||
siphasher = "0.3.9"
|
||||
smallvec = "1.8.0"
|
||||
smartstring = "1"
|
||||
sourcemap = "8.0.0"
|
||||
sourcemap = "8.0.1"
|
||||
st-map = "0.2.0"
|
||||
syn = "2"
|
||||
tempfile = "3.6.0"
|
||||
|
@ -6,7 +6,6 @@
|
||||
"x",
|
||||
"Error"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"foo"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"foo"
|
||||
],
|
||||
"rangeMappings": ";;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -7,7 +7,6 @@
|
||||
"console",
|
||||
"log"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"a"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"a"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"a"
|
||||
],
|
||||
"rangeMappings": ";;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -8,7 +8,6 @@
|
||||
"resolve",
|
||||
"setTimeout"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -15,7 +15,6 @@
|
||||
"customElements",
|
||||
"define"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -4,7 +4,6 @@
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -8,4 +8,4 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJyYW5nZU1hcHBpbmdzIjoiIiwibWFwcGluZ3MiOiIifQ==
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"test"
|
||||
],
|
||||
"rangeMappings": ";;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"test"
|
||||
],
|
||||
"rangeMappings": ";;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -4,7 +4,6 @@
|
||||
"console",
|
||||
"log"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -6,7 +6,6 @@
|
||||
"foo",
|
||||
"Base"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/source/a/a.ts"
|
||||
],
|
||||
|
@ -10,7 +10,6 @@
|
||||
"toBe",
|
||||
"foo"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/source/a/a.spec.ts"
|
||||
],
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"mappings": ";;;;;uBAAc;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA;uBACA",
|
||||
"names": [],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -7,7 +7,6 @@
|
||||
"document",
|
||||
"getElementById"
|
||||
],
|
||||
"rangeMappings": ";;;;",
|
||||
"sources": [
|
||||
"../../input/index.tsx"
|
||||
],
|
||||
|
@ -1,3 +1,3 @@
|
||||
var a = "//# sourceMappingURL=[file].map";
|
||||
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL2lucHV0LzEuanMiXSwic291cmNlc0NvbnRlbnQiOlsidmFyIGEgPSBcIi8vIyBzb3VyY2VNYXBwaW5nVVJMPVtmaWxlXS5tYXBcIlxuIl0sIm5hbWVzIjpbImEiXSwicmFuZ2VNYXBwaW5ncyI6IiIsIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFJIn0=
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL2lucHV0LzEuanMiXSwic291cmNlc0NvbnRlbnQiOlsidmFyIGEgPSBcIi8vIyBzb3VyY2VNYXBwaW5nVVJMPVtmaWxlXS5tYXBcIlxuIl0sIm5hbWVzIjpbImEiXSwibWFwcGluZ3MiOiJBQUFBLElBQUlBLElBQUkifQ==
|
||||
|
@ -9,7 +9,6 @@
|
||||
"expect",
|
||||
"toContain"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -7,7 +7,6 @@
|
||||
"stack",
|
||||
"split"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/util.ts"
|
||||
],
|
||||
|
@ -13,4 +13,4 @@ const Button = (0, _linaria.css)`
|
||||
color: red;
|
||||
`;
|
||||
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL2lucHV0L2luZGV4LnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNzcyB9IGZyb20gXCJsaW5hcmlhXCI7XG5cbmV4cG9ydCBjb25zdCBCdXR0b24gPSBjc3NgXG4gICAgY29sb3I6IHJlZDtcbmA7XG4iXSwibmFtZXMiOlsiQnV0dG9uIiwiY3NzIl0sInJhbmdlTWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7IiwibWFwcGluZ3MiOiI7Ozs7K0JBRWFBOzs7ZUFBQUE7Ozt5QkFGTztBQUViLE1BQU1BLFNBQVNDLElBQUFBLFlBQUcsQ0FBQSxDQUFDOztBQUUxQixDQUFDIn0=
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL2lucHV0L2luZGV4LnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNzcyB9IGZyb20gXCJsaW5hcmlhXCI7XG5cbmV4cG9ydCBjb25zdCBCdXR0b24gPSBjc3NgXG4gICAgY29sb3I6IHJlZDtcbmA7XG4iXSwibmFtZXMiOlsiQnV0dG9uIiwiY3NzIl0sIm1hcHBpbmdzIjoiOzs7OytCQUVhQTs7O2VBQUFBOzs7eUJBRk87QUFFYixNQUFNQSxTQUFTQyxJQUFBQSxZQUFHLENBQUEsQ0FBQzs7QUFFMUIsQ0FBQyJ9
|
||||
|
@ -42,7 +42,6 @@
|
||||
"prop",
|
||||
"get"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/box-model.ts"
|
||||
],
|
||||
|
@ -4,7 +4,6 @@
|
||||
"foo",
|
||||
"arr"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -8,7 +8,6 @@
|
||||
"getStaticProps",
|
||||
"props"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -8,7 +8,6 @@
|
||||
"getStaticProps",
|
||||
"props"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -25,7 +25,6 @@
|
||||
"s",
|
||||
"_N_E"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -27,7 +27,6 @@
|
||||
"isSharp",
|
||||
"toBeLessThan"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -27,7 +27,6 @@
|
||||
"isSharp",
|
||||
"toBeLessThan"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -6,7 +6,6 @@
|
||||
"console",
|
||||
"log"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -6,7 +6,6 @@
|
||||
"expect",
|
||||
"toBe"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -6,7 +6,6 @@
|
||||
"Error",
|
||||
"stack"
|
||||
],
|
||||
"rangeMappings": ";;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"mappings": "AAAA,cAAc,iBAAiB;AAC/B,cAAc,wBAAwB;AAEtC,cAAc,WAAW;AACzB,cAAc,eAAe",
|
||||
"names": [],
|
||||
"rangeMappings": ";;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -29,7 +29,6 @@
|
||||
"deleteComment",
|
||||
"delete"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/CommentControlller.ts"
|
||||
],
|
||||
|
@ -51,7 +51,6 @@
|
||||
"$pull",
|
||||
"name"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/CommentService.ts"
|
||||
],
|
||||
|
@ -48,7 +48,6 @@
|
||||
"createPostComment",
|
||||
"createCommentDto"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/PistController.ts"
|
||||
],
|
||||
|
@ -31,7 +31,6 @@
|
||||
"user",
|
||||
"id"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/UserController.ts"
|
||||
],
|
||||
|
@ -6,7 +6,6 @@
|
||||
"expect",
|
||||
"toBe"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -7,7 +7,6 @@
|
||||
"toBe",
|
||||
"str"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -5,7 +5,6 @@
|
||||
"Symbol",
|
||||
"for"
|
||||
],
|
||||
"rangeMappings": ";;",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"mappings": "AAAA,cAAc,iBAAiB;AAC/B,cAAc,wBAAwB;AACtC,cAAc,eAAe;AAC7B,cAAc,WAAW;AACzB,cAAc,eAAe",
|
||||
"names": [],
|
||||
"rangeMappings": ";;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"a"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -3,7 +3,6 @@
|
||||
"names": [
|
||||
"a"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -45,7 +45,6 @@
|
||||
"variant",
|
||||
"constructor"
|
||||
],
|
||||
"rangeMappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
||||
"sources": [
|
||||
"../../input/index.ts"
|
||||
],
|
||||
|
File diff suppressed because one or more lines are too long
@ -7,7 +7,6 @@
|
||||
"message",
|
||||
"bbb"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"unknown"
|
||||
],
|
||||
|
@ -7,7 +7,6 @@
|
||||
"message",
|
||||
"bbb"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"../../input/index.js"
|
||||
],
|
||||
|
@ -5,7 +5,6 @@
|
||||
"console",
|
||||
"log"
|
||||
],
|
||||
"rangeMappings": ";",
|
||||
"sources": [
|
||||
"$DIR/tests/minify/issue-7475/1-with-preamble/input.js"
|
||||
],
|
||||
|
@ -5,7 +5,6 @@
|
||||
"console",
|
||||
"log"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"$DIR/tests/minify/issue-7475/2-no-preamble/input.js"
|
||||
],
|
||||
|
@ -6,7 +6,6 @@
|
||||
"aób",
|
||||
"a"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"input-preprocess.js"
|
||||
],
|
||||
|
@ -6,7 +6,6 @@
|
||||
"console",
|
||||
"log"
|
||||
],
|
||||
"rangeMappings": "",
|
||||
"sources": [
|
||||
"$DIR/tests/minify/issue-7475/issue-8372/2/input.js"
|
||||
],
|
||||
|
@ -49,6 +49,25 @@ impl Take for ModuleDecl {
|
||||
}
|
||||
}
|
||||
|
||||
/// Default exports other than **direct** function expression or class
|
||||
/// expression.
|
||||
///
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// ```ts
|
||||
/// export default function Foo() {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// is [`ExportDefaultDecl`] and it's hoisted.
|
||||
///
|
||||
/// ```ts
|
||||
/// export default (function Foo() {
|
||||
/// })
|
||||
/// ```
|
||||
///
|
||||
/// is [`ExportDefaultExpr`] and it's not hoisted.
|
||||
#[ast_node("ExportDefaultExpression")]
|
||||
#[derive(Eq, Hash, EqIgnoreSpan)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
|
@ -9,3 +9,14 @@ repository.workspace = true
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
|
||||
swc_atoms = { version = "0.6.7", path = "../swc_atoms" }
|
||||
swc_common = { version = "0.34.2", path = "../swc_common" }
|
||||
swc_ecma_ast = { version = "0.115.1", path = "../swc_ecma_ast" }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
swc_ecma_codegen = { version = "0.151.0", path = "../swc_ecma_codegen" }
|
||||
swc_ecma_parser = { version = "0.146.2", path = "../swc_ecma_parser" }
|
||||
testing = { version = "0.36.0", path = "../testing" }
|
||||
|
35
crates/swc_typescript/src/diagnostic.rs
Normal file
35
crates/swc_typescript/src/diagnostic.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use swc_common::{FileName, Span};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SourceRange {
|
||||
pub filename: Arc<FileName>,
|
||||
pub range: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum DtsIssue {
|
||||
#[error("unable to infer type from expression or declaration")]
|
||||
UnableToInferType { range: SourceRange },
|
||||
#[error("unable to infer type, falling back to any type")]
|
||||
UnableToInferTypeFallbackAny { range: SourceRange },
|
||||
#[error("unable to infer type from object property, skipping")]
|
||||
UnableToInferTypeFromProp { range: SourceRange },
|
||||
#[error("unable to infer type from spread, skipping")]
|
||||
UnableToInferTypeFromSpread { range: SourceRange },
|
||||
#[error("cannot infer type from using, skipping")]
|
||||
UnsupportedUsing { range: SourceRange },
|
||||
}
|
||||
|
||||
impl DtsIssue {
|
||||
pub fn range(&self) -> Option<&SourceRange> {
|
||||
match self {
|
||||
DtsIssue::UnableToInferType { range } => Some(range),
|
||||
DtsIssue::UnableToInferTypeFallbackAny { range } => Some(range),
|
||||
DtsIssue::UnableToInferTypeFromProp { range } => Some(range),
|
||||
DtsIssue::UnableToInferTypeFromSpread { range } => Some(range),
|
||||
DtsIssue::UnsupportedUsing { range } => Some(range),
|
||||
}
|
||||
}
|
||||
}
|
929
crates/swc_typescript/src/fast_dts/mod.rs
Normal file
929
crates/swc_typescript/src/fast_dts/mod.rs
Normal file
@ -0,0 +1,929 @@
|
||||
use std::{mem::take, sync::Arc};
|
||||
|
||||
use swc_atoms::Atom;
|
||||
use swc_common::{util::take::Take, FileName, Span, Spanned, DUMMY_SP};
|
||||
use swc_ecma_ast::{
|
||||
BindingIdent, ClassMember, Decl, DefaultDecl, ExportDecl, ExportDefaultDecl, ExportDefaultExpr,
|
||||
Expr, FnDecl, FnExpr, Ident, Lit, MethodKind, Module, ModuleDecl, ModuleItem, OptChainBase,
|
||||
Pat, Prop, PropName, PropOrSpread, Stmt, TsEntityName, TsFnOrConstructorType, TsFnParam,
|
||||
TsFnType, TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType, TsNamespaceBody,
|
||||
TsPropertySignature, TsTupleElement, TsTupleType, TsType, TsTypeAnn, TsTypeElement, TsTypeLit,
|
||||
TsTypeOperator, TsTypeOperatorOp, TsTypeRef, VarDecl, VarDeclKind, VarDeclarator,
|
||||
};
|
||||
|
||||
use crate::diagnostic::{DtsIssue, SourceRange};
|
||||
|
||||
/// TypeScript Isolated Declaration support.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// # License
|
||||
///
|
||||
/// Mostly copied from <https://github.com/denoland/deno_graph/blob/15db6e5fb6d3faea027e16c3d9ce6498b11beed2/src/fast_check/transform_dts.rs>
|
||||
///
|
||||
/// The original code is MIT licensed.
|
||||
|
||||
pub struct FastDts {
|
||||
filename: Arc<FileName>,
|
||||
is_top_level: bool,
|
||||
id_counter: u32,
|
||||
diagnostics: Vec<DtsIssue>,
|
||||
}
|
||||
|
||||
/// Diagnostics
|
||||
impl FastDts {
|
||||
pub fn new(filename: Arc<FileName>) -> Self {
|
||||
Self {
|
||||
filename,
|
||||
is_top_level: false,
|
||||
id_counter: 0,
|
||||
diagnostics: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_diagnostic(&mut self, diagnostic: DtsIssue) {
|
||||
self.diagnostics.push(diagnostic)
|
||||
}
|
||||
|
||||
fn source_range_to_range(&self, range: Span) -> SourceRange {
|
||||
SourceRange {
|
||||
filename: self.filename.clone(),
|
||||
range,
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_diagnostic_unable_to_infer(&mut self, range: Span) {
|
||||
self.mark_diagnostic(DtsIssue::UnableToInferType {
|
||||
range: self.source_range_to_range(range),
|
||||
})
|
||||
}
|
||||
|
||||
fn mark_diagnostic_any_fallback(&mut self, range: Span) {
|
||||
self.mark_diagnostic(DtsIssue::UnableToInferTypeFallbackAny {
|
||||
range: self.source_range_to_range(range),
|
||||
})
|
||||
}
|
||||
|
||||
fn mark_diagnostic_unsupported_prop(&mut self, range: Span) {
|
||||
self.mark_diagnostic(DtsIssue::UnableToInferTypeFromProp {
|
||||
range: self.source_range_to_range(range),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FastDts {
|
||||
pub fn transform(&mut self, module: &mut Module) -> Vec<DtsIssue> {
|
||||
self.is_top_level = true;
|
||||
|
||||
self.transform_module_items(&mut module.body);
|
||||
|
||||
take(&mut self.diagnostics)
|
||||
}
|
||||
|
||||
fn transform_module_items(&mut self, items: &mut Vec<ModuleItem>) {
|
||||
let orig_items = take(items);
|
||||
let mut new_items = Vec::with_capacity(orig_items.len());
|
||||
|
||||
let mut prev_is_overload = false;
|
||||
|
||||
for mut item in orig_items {
|
||||
let is_overload = match &item {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, .. }))
|
||||
| ModuleItem::Stmt(Stmt::Decl(decl)) => match decl {
|
||||
Decl::Fn(FnDecl {
|
||||
function, declare, ..
|
||||
}) => !declare && function.body.is_none(),
|
||||
_ => false,
|
||||
},
|
||||
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
|
||||
decl,
|
||||
..
|
||||
})) => {
|
||||
matches!(decl, DefaultDecl::Fn(FnExpr { function, .. }) if function.body.is_none())
|
||||
}
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
match &mut item {
|
||||
// Keep all these
|
||||
ModuleItem::ModuleDecl(
|
||||
ModuleDecl::Import(..)
|
||||
| ModuleDecl::TsImportEquals(_)
|
||||
| ModuleDecl::TsNamespaceExport(_)
|
||||
| ModuleDecl::TsExportAssignment(_)
|
||||
| ModuleDecl::ExportNamed(_)
|
||||
| ModuleDecl::ExportAll(_),
|
||||
) => new_items.push(item),
|
||||
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
span, decl, ..
|
||||
})) => {
|
||||
let should_keep = prev_is_overload && !is_overload;
|
||||
|
||||
if should_keep {
|
||||
prev_is_overload = is_overload;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(()) = self.decl_to_type_decl(decl) {
|
||||
new_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(
|
||||
ExportDecl {
|
||||
decl: decl.take(),
|
||||
span: *span,
|
||||
},
|
||||
)));
|
||||
} else {
|
||||
self.mark_diagnostic(DtsIssue::UnableToInferType {
|
||||
range: self.source_range_to_range(*span),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => {
|
||||
match &mut export.decl {
|
||||
DefaultDecl::Class(class_expr) => {
|
||||
self.class_body_to_type(&mut class_expr.class.body);
|
||||
}
|
||||
DefaultDecl::Fn(fn_expr) => {
|
||||
fn_expr.function.body = None;
|
||||
}
|
||||
DefaultDecl::TsInterfaceDecl(_) => {}
|
||||
};
|
||||
|
||||
let should_keep = prev_is_overload && !is_overload;
|
||||
prev_is_overload = is_overload;
|
||||
if should_keep {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_items.push(item);
|
||||
}
|
||||
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => {
|
||||
let should_keep = prev_is_overload && !is_overload;
|
||||
prev_is_overload = is_overload;
|
||||
if should_keep {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = self.gen_unique_name();
|
||||
let name_ident = Ident::new(name, DUMMY_SP);
|
||||
let type_ann = self
|
||||
.expr_to_ts_type(export.expr.clone(), false, true)
|
||||
.map(type_ann);
|
||||
|
||||
if let Some(type_ann) = type_ann {
|
||||
new_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(
|
||||
VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Const,
|
||||
declare: true,
|
||||
decls: vec![VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
name: Pat::Ident(BindingIdent {
|
||||
id: name_ident.clone(),
|
||||
type_ann: Some(type_ann),
|
||||
}),
|
||||
init: None,
|
||||
definite: false,
|
||||
}],
|
||||
},
|
||||
)))));
|
||||
|
||||
new_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
|
||||
ExportDefaultExpr {
|
||||
span: export.span,
|
||||
expr: Box::new(Expr::Ident(name_ident)),
|
||||
},
|
||||
)))
|
||||
} else {
|
||||
new_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
|
||||
ExportDefaultExpr {
|
||||
span: export.span,
|
||||
expr: export.expr.take(),
|
||||
},
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
ModuleItem::Stmt(Stmt::Decl(decl)) => match decl {
|
||||
Decl::TsEnum(_)
|
||||
| Decl::Class(_)
|
||||
| Decl::Fn(_)
|
||||
| Decl::Var(_)
|
||||
| Decl::TsModule(_) => {
|
||||
if let Some(()) = self.decl_to_type_decl(decl) {
|
||||
new_items.push(item);
|
||||
} else {
|
||||
self.mark_diagnostic_unable_to_infer(decl.span())
|
||||
}
|
||||
}
|
||||
|
||||
Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::Using(_) => {
|
||||
new_items.push(item);
|
||||
}
|
||||
},
|
||||
|
||||
ModuleItem::Stmt(..) => {}
|
||||
}
|
||||
|
||||
prev_is_overload = is_overload;
|
||||
}
|
||||
|
||||
*items = new_items;
|
||||
}
|
||||
|
||||
fn expr_to_ts_type(
|
||||
&mut self,
|
||||
e: Box<Expr>,
|
||||
as_const: bool,
|
||||
as_readonly: bool,
|
||||
) -> Option<Box<TsType>> {
|
||||
match *e {
|
||||
Expr::Array(arr) => {
|
||||
let mut elem_types: Vec<TsTupleElement> = vec![];
|
||||
|
||||
for elems in arr.elems {
|
||||
if let Some(expr_or_spread) = elems {
|
||||
let span = expr_or_spread.span();
|
||||
if let Some(ts_expr) =
|
||||
self.expr_to_ts_type(expr_or_spread.expr, as_const, as_readonly)
|
||||
{
|
||||
elem_types.push(ts_tuple_element(ts_expr));
|
||||
} else {
|
||||
self.mark_diagnostic_unable_to_infer(span);
|
||||
}
|
||||
} else {
|
||||
// TypeScript converts holey arrays to any
|
||||
// Example: const a = [,,] -> const a = [any, any, any]
|
||||
elem_types.push(ts_tuple_element(Box::new(TsType::TsKeywordType(
|
||||
TsKeywordType {
|
||||
kind: TsKeywordTypeKind::TsAnyKeyword,
|
||||
span: DUMMY_SP,
|
||||
},
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = Box::new(TsType::TsTupleType(TsTupleType {
|
||||
span: arr.span,
|
||||
elem_types,
|
||||
}));
|
||||
|
||||
if as_readonly {
|
||||
result = ts_readonly(result);
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
Expr::Object(obj) => {
|
||||
let mut members: Vec<TsTypeElement> = vec![];
|
||||
|
||||
// TODO: Prescan all object properties to know which ones
|
||||
// have a getter or a setter. This allows us to apply
|
||||
// TypeScript's `readonly` keyword accordingly.
|
||||
|
||||
for item in obj.props {
|
||||
match item {
|
||||
PropOrSpread::Prop(prop_box) => {
|
||||
let prop = *prop_box;
|
||||
match prop {
|
||||
Prop::KeyValue(key_value) => {
|
||||
let (key, computed) = match key_value.key {
|
||||
PropName::Ident(ident) => (Expr::Ident(ident), false),
|
||||
PropName::Str(str_prop) => {
|
||||
(Expr::Lit(Lit::Str(str_prop)), false)
|
||||
}
|
||||
PropName::Num(num) => (Expr::Lit(Lit::Num(num)), true),
|
||||
PropName::Computed(computed) => (*computed.expr, true),
|
||||
PropName::BigInt(big_int) => {
|
||||
(Expr::Lit(Lit::BigInt(big_int)), true)
|
||||
}
|
||||
};
|
||||
|
||||
let init_type = self
|
||||
.expr_to_ts_type(key_value.value, as_const, as_readonly)
|
||||
.map(type_ann);
|
||||
|
||||
members.push(TsTypeElement::TsPropertySignature(
|
||||
TsPropertySignature {
|
||||
span: DUMMY_SP,
|
||||
readonly: as_readonly,
|
||||
key: Box::new(key),
|
||||
computed,
|
||||
optional: false,
|
||||
type_ann: init_type,
|
||||
},
|
||||
));
|
||||
}
|
||||
Prop::Shorthand(_)
|
||||
| Prop::Assign(_)
|
||||
| Prop::Getter(_)
|
||||
| Prop::Setter(_)
|
||||
| Prop::Method(_) => {
|
||||
self.mark_diagnostic_unsupported_prop(prop.span());
|
||||
}
|
||||
}
|
||||
}
|
||||
PropOrSpread::Spread(_) => {
|
||||
self.mark_diagnostic(DtsIssue::UnableToInferTypeFromSpread {
|
||||
range: self.source_range_to_range(item.span()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Box::new(TsType::TsTypeLit(TsTypeLit {
|
||||
span: obj.span,
|
||||
members,
|
||||
})))
|
||||
}
|
||||
Expr::Lit(lit) => {
|
||||
if as_const {
|
||||
maybe_lit_to_ts_type_const(&lit)
|
||||
} else {
|
||||
maybe_lit_to_ts_type(&lit)
|
||||
}
|
||||
}
|
||||
Expr::TsConstAssertion(ts_const) => self.expr_to_ts_type(ts_const.expr, true, true),
|
||||
Expr::TsSatisfies(satisifies) => {
|
||||
self.expr_to_ts_type(satisifies.expr, as_const, as_readonly)
|
||||
}
|
||||
Expr::TsAs(ts_as) => Some(ts_as.type_ann),
|
||||
Expr::Fn(fn_expr) => {
|
||||
let return_type = fn_expr
|
||||
.function
|
||||
.return_type
|
||||
.map_or(any_type_ann(), |val| val);
|
||||
|
||||
let params: Vec<TsFnParam> = fn_expr
|
||||
.function
|
||||
.params
|
||||
.into_iter()
|
||||
.filter_map(|param| self.pat_to_ts_fn_param(param.pat))
|
||||
.collect();
|
||||
|
||||
Some(Box::new(TsType::TsFnOrConstructorType(
|
||||
TsFnOrConstructorType::TsFnType(TsFnType {
|
||||
span: fn_expr.function.span,
|
||||
params,
|
||||
type_ann: return_type,
|
||||
type_params: fn_expr.function.type_params,
|
||||
}),
|
||||
)))
|
||||
}
|
||||
Expr::Arrow(arrow_expr) => {
|
||||
let return_type = arrow_expr.return_type.map_or(any_type_ann(), |val| val);
|
||||
|
||||
let params = arrow_expr
|
||||
.params
|
||||
.into_iter()
|
||||
.filter_map(|pat| self.pat_to_ts_fn_param(pat))
|
||||
.collect();
|
||||
|
||||
Some(Box::new(TsType::TsFnOrConstructorType(
|
||||
TsFnOrConstructorType::TsFnType(TsFnType {
|
||||
span: arrow_expr.span,
|
||||
params,
|
||||
type_ann: return_type,
|
||||
type_params: arrow_expr.type_params,
|
||||
}),
|
||||
)))
|
||||
}
|
||||
// Since fast check requires explicit type annotations these
|
||||
// can be dropped as they are not part of an export declaration
|
||||
Expr::This(_)
|
||||
| Expr::Unary(_)
|
||||
| Expr::Update(_)
|
||||
| Expr::Bin(_)
|
||||
| Expr::Assign(_)
|
||||
| Expr::Member(_)
|
||||
| Expr::SuperProp(_)
|
||||
| Expr::Cond(_)
|
||||
| Expr::Call(_)
|
||||
| Expr::New(_)
|
||||
| Expr::Seq(_)
|
||||
| Expr::Ident(_)
|
||||
| Expr::Tpl(_)
|
||||
| Expr::TaggedTpl(_)
|
||||
| Expr::Class(_)
|
||||
| Expr::Yield(_)
|
||||
| Expr::MetaProp(_)
|
||||
| Expr::Await(_)
|
||||
| Expr::Paren(_)
|
||||
| Expr::JSXMember(_)
|
||||
| Expr::JSXNamespacedName(_)
|
||||
| Expr::JSXEmpty(_)
|
||||
| Expr::JSXElement(_)
|
||||
| Expr::JSXFragment(_)
|
||||
| Expr::TsTypeAssertion(_)
|
||||
| Expr::TsNonNull(_)
|
||||
| Expr::TsInstantiation(_)
|
||||
| Expr::PrivateName(_)
|
||||
| Expr::OptChain(_)
|
||||
| Expr::Invalid(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn decl_to_type_decl(&mut self, decl: &mut Decl) -> Option<()> {
|
||||
let is_declare = self.is_top_level;
|
||||
match decl {
|
||||
Decl::Class(class_decl) => {
|
||||
self.class_body_to_type(&mut class_decl.class.body);
|
||||
class_decl.declare = is_declare;
|
||||
Some(())
|
||||
}
|
||||
Decl::Fn(fn_decl) => {
|
||||
fn_decl.function.body = None;
|
||||
fn_decl.declare = is_declare;
|
||||
|
||||
for param in &mut fn_decl.function.params {
|
||||
match &mut param.pat {
|
||||
Pat::Ident(ident) => {
|
||||
if ident.type_ann.is_none() {
|
||||
self.mark_diagnostic_any_fallback(ident.span());
|
||||
ident.type_ann = Some(any_type_ann());
|
||||
}
|
||||
}
|
||||
Pat::Assign(assign_pat) => {
|
||||
match &mut *assign_pat.left {
|
||||
Pat::Ident(ident) => {
|
||||
if ident.type_ann.is_none() {
|
||||
ident.type_ann = self.infer_expr_fallback_any(
|
||||
assign_pat.right.take(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
ident.optional = true;
|
||||
param.pat = Pat::Ident(ident.clone());
|
||||
}
|
||||
Pat::Array(arr_pat) => {
|
||||
if arr_pat.type_ann.is_none() {
|
||||
arr_pat.type_ann = self.infer_expr_fallback_any(
|
||||
assign_pat.right.take(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
arr_pat.optional = true;
|
||||
param.pat = Pat::Array(arr_pat.clone());
|
||||
}
|
||||
Pat::Object(obj_pat) => {
|
||||
if obj_pat.type_ann.is_none() {
|
||||
obj_pat.type_ann = self.infer_expr_fallback_any(
|
||||
assign_pat.right.take(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
obj_pat.optional = true;
|
||||
param.pat = Pat::Object(obj_pat.clone());
|
||||
}
|
||||
Pat::Rest(_) | Pat::Assign(_) | Pat::Expr(_) | Pat::Invalid(_) => {}
|
||||
};
|
||||
}
|
||||
Pat::Array(_)
|
||||
| Pat::Rest(_)
|
||||
| Pat::Object(_)
|
||||
| Pat::Invalid(_)
|
||||
| Pat::Expr(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
Decl::Var(var_decl) => {
|
||||
var_decl.declare = is_declare;
|
||||
|
||||
for decl in &mut var_decl.decls {
|
||||
if let Pat::Ident(ident) = &mut decl.name {
|
||||
if ident.type_ann.is_some() {
|
||||
decl.init = None;
|
||||
continue;
|
||||
}
|
||||
|
||||
let ts_type = decl
|
||||
.init
|
||||
.take()
|
||||
.and_then(|init| self.expr_to_ts_type(init, false, true))
|
||||
.map(type_ann)
|
||||
.or_else(|| {
|
||||
self.mark_diagnostic_any_fallback(ident.span());
|
||||
Some(any_type_ann())
|
||||
});
|
||||
ident.type_ann = ts_type;
|
||||
} else {
|
||||
self.mark_diagnostic_unable_to_infer(decl.span());
|
||||
}
|
||||
|
||||
decl.init = None;
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
Decl::TsEnum(ts_enum) => {
|
||||
ts_enum.declare = is_declare;
|
||||
|
||||
for member in &mut ts_enum.members {
|
||||
if let Some(init) = &member.init {
|
||||
// Support for expressions is limited in enums,
|
||||
// see https://www.typescriptlang.org/docs/handbook/enums.html
|
||||
member.init = if self.valid_enum_init_expr(init) {
|
||||
Some(init.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
Decl::TsModule(ts_module) => {
|
||||
ts_module.declare = is_declare;
|
||||
|
||||
if let Some(body) = ts_module.body.take() {
|
||||
ts_module.body = Some(self.transform_ts_ns_body(body));
|
||||
|
||||
Some(())
|
||||
} else {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
Decl::TsInterface(_) | Decl::TsTypeAlias(_) => Some(()),
|
||||
Decl::Using(_) => {
|
||||
self.mark_diagnostic(DtsIssue::UnsupportedUsing {
|
||||
range: self.source_range_to_range(decl.span()),
|
||||
});
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_ts_ns_body(&mut self, ns: TsNamespaceBody) -> TsNamespaceBody {
|
||||
let original_is_top_level = self.is_top_level;
|
||||
self.is_top_level = false;
|
||||
let body = match ns {
|
||||
TsNamespaceBody::TsModuleBlock(mut ts_module_block) => {
|
||||
self.transform_module_items(&mut ts_module_block.body);
|
||||
TsNamespaceBody::TsModuleBlock(ts_module_block)
|
||||
}
|
||||
TsNamespaceBody::TsNamespaceDecl(ts_ns) => self.transform_ts_ns_body(*ts_ns.body),
|
||||
};
|
||||
self.is_top_level = original_is_top_level;
|
||||
body
|
||||
}
|
||||
|
||||
// Support for expressions is limited in enums,
|
||||
// see https://www.typescriptlang.org/docs/handbook/enums.html
|
||||
fn valid_enum_init_expr(&mut self, expr: &Expr) -> bool {
|
||||
match expr {
|
||||
Expr::Bin(bin_expr) => {
|
||||
if !self.valid_enum_init_expr(&bin_expr.left) {
|
||||
false
|
||||
} else {
|
||||
self.valid_enum_init_expr(&bin_expr.right)
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Member(member_expr) => self.valid_enum_init_expr(&member_expr.obj),
|
||||
Expr::OptChain(opt_expr) => match &*opt_expr.base {
|
||||
OptChainBase::Member(member_expr) => {
|
||||
self.valid_enum_init_expr(&Expr::Member(member_expr.clone()))
|
||||
}
|
||||
OptChainBase::Call(_) => false,
|
||||
},
|
||||
// TS does infer the type of identifiers
|
||||
Expr::Ident(_) => true,
|
||||
Expr::Lit(lit) => match lit {
|
||||
Lit::Num(_) | Lit::Str(_) => true,
|
||||
Lit::Bool(_) | Lit::Null(_) | Lit::BigInt(_) | Lit::Regex(_) | Lit::JSXText(_) => {
|
||||
false
|
||||
}
|
||||
},
|
||||
Expr::Tpl(tpl_expr) => {
|
||||
for expr in &tpl_expr.exprs {
|
||||
if !self.valid_enum_init_expr(expr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
Expr::Paren(paren_expr) => self.valid_enum_init_expr(&paren_expr.expr),
|
||||
|
||||
Expr::TsTypeAssertion(ts_ass) => {
|
||||
// Only assertions to number are allowed for computed
|
||||
// enum members.
|
||||
match &*ts_ass.type_ann {
|
||||
TsType::TsLitType(ts_lit) => match ts_lit.lit {
|
||||
TsLit::Number(_) => true,
|
||||
TsLit::Str(_) | TsLit::Bool(_) | TsLit::BigInt(_) | TsLit::Tpl(_) => false,
|
||||
},
|
||||
TsType::TsKeywordType(_)
|
||||
| TsType::TsThisType(_)
|
||||
| TsType::TsFnOrConstructorType(_)
|
||||
| TsType::TsTypeRef(_)
|
||||
| TsType::TsTypeQuery(_)
|
||||
| TsType::TsTypeLit(_)
|
||||
| TsType::TsArrayType(_)
|
||||
| TsType::TsTupleType(_)
|
||||
| TsType::TsOptionalType(_)
|
||||
| TsType::TsRestType(_)
|
||||
| TsType::TsUnionOrIntersectionType(_)
|
||||
| TsType::TsConditionalType(_)
|
||||
| TsType::TsInferType(_)
|
||||
| TsType::TsParenthesizedType(_)
|
||||
| TsType::TsTypeOperator(_)
|
||||
| TsType::TsIndexedAccessType(_)
|
||||
| TsType::TsMappedType(_)
|
||||
| TsType::TsTypePredicate(_)
|
||||
| TsType::TsImportType(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
Expr::TsAs(ts_as) => self.valid_enum_ts_type(&ts_as.type_ann),
|
||||
|
||||
// These are not valid as enum member initializer and
|
||||
// TS will throw a type error. For declaration generation
|
||||
// they will be dropped in TS so we do that too.
|
||||
Expr::TsInstantiation(_)
|
||||
| Expr::Call(_)
|
||||
| Expr::Update(_)
|
||||
| Expr::PrivateName(_)
|
||||
| Expr::TsSatisfies(_)
|
||||
| Expr::TsNonNull(_)
|
||||
| Expr::TsConstAssertion(_)
|
||||
| Expr::Cond(_)
|
||||
| Expr::Seq(_)
|
||||
| Expr::TaggedTpl(_)
|
||||
| Expr::Object(_)
|
||||
| Expr::Array(_)
|
||||
| Expr::Arrow(_)
|
||||
| Expr::Class(_)
|
||||
| Expr::Await(_)
|
||||
| Expr::MetaProp(_)
|
||||
| Expr::New(_)
|
||||
| Expr::JSXMember(_)
|
||||
| Expr::JSXNamespacedName(_)
|
||||
| Expr::JSXEmpty(_)
|
||||
| Expr::JSXElement(_)
|
||||
| Expr::JSXFragment(_)
|
||||
| Expr::Unary(_)
|
||||
| Expr::Assign(_)
|
||||
| Expr::Yield(_)
|
||||
| Expr::SuperProp(_)
|
||||
| Expr::Fn(_)
|
||||
| Expr::This(_)
|
||||
| Expr::Invalid(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_enum_ts_type(&mut self, ts_type: &TsType) -> bool {
|
||||
match ts_type {
|
||||
TsType::TsLitType(ts_lit) => match ts_lit.lit {
|
||||
TsLit::Number(_) => true,
|
||||
TsLit::Str(_) | TsLit::Bool(_) | TsLit::BigInt(_) | TsLit::Tpl(_) => false,
|
||||
},
|
||||
TsType::TsKeywordType(_)
|
||||
| TsType::TsThisType(_)
|
||||
| TsType::TsFnOrConstructorType(_)
|
||||
| TsType::TsTypeRef(_)
|
||||
| TsType::TsTypeQuery(_)
|
||||
| TsType::TsTypeLit(_)
|
||||
| TsType::TsArrayType(_)
|
||||
| TsType::TsTupleType(_)
|
||||
| TsType::TsOptionalType(_)
|
||||
| TsType::TsRestType(_)
|
||||
| TsType::TsUnionOrIntersectionType(_)
|
||||
| TsType::TsConditionalType(_)
|
||||
| TsType::TsInferType(_)
|
||||
| TsType::TsParenthesizedType(_)
|
||||
| TsType::TsTypeOperator(_)
|
||||
| TsType::TsIndexedAccessType(_)
|
||||
| TsType::TsMappedType(_)
|
||||
| TsType::TsTypePredicate(_)
|
||||
| TsType::TsImportType(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_expr_fallback_any(
|
||||
&mut self,
|
||||
expr: Box<Expr>,
|
||||
as_const: bool,
|
||||
as_readonly: bool,
|
||||
) -> Option<Box<TsTypeAnn>> {
|
||||
let span = expr.span();
|
||||
|
||||
if let Some(ts_type) = self.expr_to_ts_type(expr, as_const, as_readonly) {
|
||||
Some(type_ann(ts_type))
|
||||
} else {
|
||||
self.mark_diagnostic_any_fallback(span);
|
||||
Some(any_type_ann())
|
||||
}
|
||||
}
|
||||
|
||||
fn class_body_to_type(&mut self, body: &mut Vec<ClassMember>) {
|
||||
// Track if the previous member was an overload signature or not.
|
||||
// When overloads are present the last item has the implementation
|
||||
// body. For declaration files the implementation always needs to
|
||||
// be dropped. Needs to be unique for each class because another
|
||||
// class could be created inside a class method.
|
||||
let mut prev_is_overload = false;
|
||||
|
||||
let new_body = body
|
||||
.take()
|
||||
.into_iter()
|
||||
.filter(|member| match member {
|
||||
ClassMember::Constructor(class_constructor) => {
|
||||
let is_overload = class_constructor.body.is_none();
|
||||
if !prev_is_overload || is_overload {
|
||||
prev_is_overload = is_overload;
|
||||
true
|
||||
} else {
|
||||
prev_is_overload = false;
|
||||
false
|
||||
}
|
||||
}
|
||||
ClassMember::Method(method) => {
|
||||
let is_overload = method.function.body.is_none();
|
||||
if !prev_is_overload || is_overload {
|
||||
prev_is_overload = is_overload;
|
||||
true
|
||||
} else {
|
||||
prev_is_overload = false;
|
||||
false
|
||||
}
|
||||
}
|
||||
ClassMember::TsIndexSignature(_)
|
||||
| ClassMember::ClassProp(_)
|
||||
| ClassMember::PrivateProp(_)
|
||||
| ClassMember::Empty(_)
|
||||
| ClassMember::StaticBlock(_)
|
||||
| ClassMember::AutoAccessor(_)
|
||||
| ClassMember::PrivateMethod(_) => {
|
||||
prev_is_overload = false;
|
||||
true
|
||||
}
|
||||
})
|
||||
.filter_map(|member| match member {
|
||||
ClassMember::Constructor(mut class_constructor) => {
|
||||
class_constructor.body = None;
|
||||
Some(ClassMember::Constructor(class_constructor))
|
||||
}
|
||||
ClassMember::Method(mut method) => {
|
||||
method.function.body = None;
|
||||
if method.kind == MethodKind::Setter {
|
||||
method.function.return_type = None;
|
||||
}
|
||||
Some(ClassMember::Method(method))
|
||||
}
|
||||
ClassMember::ClassProp(mut prop) => {
|
||||
if prop.type_ann.is_none() {
|
||||
if let Some(value) = prop.value {
|
||||
prop.type_ann = self
|
||||
.expr_to_ts_type(value, false, false)
|
||||
.map(type_ann)
|
||||
.or_else(|| Some(any_type_ann()));
|
||||
}
|
||||
}
|
||||
prop.value = None;
|
||||
prop.definite = false;
|
||||
prop.declare = false;
|
||||
|
||||
Some(ClassMember::ClassProp(prop))
|
||||
}
|
||||
ClassMember::TsIndexSignature(index_sig) => {
|
||||
Some(ClassMember::TsIndexSignature(index_sig))
|
||||
}
|
||||
|
||||
// These can be removed as they are not relevant for types
|
||||
ClassMember::PrivateMethod(_)
|
||||
| ClassMember::PrivateProp(_)
|
||||
| ClassMember::Empty(_)
|
||||
| ClassMember::StaticBlock(_)
|
||||
| ClassMember::AutoAccessor(_) => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
*body = new_body;
|
||||
}
|
||||
|
||||
fn pat_to_ts_fn_param(&mut self, pat: Pat) -> Option<TsFnParam> {
|
||||
match pat {
|
||||
Pat::Ident(binding_id) => Some(TsFnParam::Ident(binding_id)),
|
||||
Pat::Array(arr_pat) => Some(TsFnParam::Array(arr_pat)),
|
||||
Pat::Rest(rest_pat) => Some(TsFnParam::Rest(rest_pat)),
|
||||
Pat::Object(obj) => Some(TsFnParam::Object(obj)),
|
||||
Pat::Assign(assign_pat) => {
|
||||
self.expr_to_ts_type(assign_pat.right, false, false)
|
||||
.map(|param| {
|
||||
let name = if let Pat::Ident(ident) = *assign_pat.left {
|
||||
ident.id.sym.clone()
|
||||
} else {
|
||||
self.gen_unique_name()
|
||||
};
|
||||
|
||||
TsFnParam::Ident(BindingIdent {
|
||||
id: Ident::new(name, assign_pat.span),
|
||||
type_ann: Some(type_ann(param)),
|
||||
})
|
||||
})
|
||||
}
|
||||
Pat::Expr(expr) => {
|
||||
self.mark_diagnostic_unable_to_infer(expr.span());
|
||||
None
|
||||
}
|
||||
// Invalid code is invalid, not sure why SWC doesn't throw
|
||||
// a parse error here.
|
||||
Pat::Invalid(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_unique_name(&mut self) -> Atom {
|
||||
self.id_counter += 1;
|
||||
format!("_dts_{}", self.id_counter).into()
|
||||
}
|
||||
}
|
||||
|
||||
fn any_type_ann() -> Box<TsTypeAnn> {
|
||||
type_ann(ts_keyword_type(TsKeywordTypeKind::TsAnyKeyword))
|
||||
}
|
||||
|
||||
fn type_ann(ts_type: Box<TsType>) -> Box<TsTypeAnn> {
|
||||
Box::new(TsTypeAnn {
|
||||
span: DUMMY_SP,
|
||||
type_ann: ts_type,
|
||||
})
|
||||
}
|
||||
|
||||
fn type_ref(name: Atom) -> TsTypeRef {
|
||||
TsTypeRef {
|
||||
span: DUMMY_SP,
|
||||
type_name: TsEntityName::Ident(Ident::new(name, DUMMY_SP)),
|
||||
type_params: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn ts_readonly(ann: Box<TsType>) -> Box<TsType> {
|
||||
Box::new(TsType::TsTypeOperator(TsTypeOperator {
|
||||
span: DUMMY_SP,
|
||||
op: TsTypeOperatorOp::ReadOnly,
|
||||
type_ann: ann,
|
||||
}))
|
||||
}
|
||||
|
||||
fn ts_tuple_element(ts_type: Box<TsType>) -> TsTupleElement {
|
||||
TsTupleElement {
|
||||
label: None,
|
||||
span: DUMMY_SP,
|
||||
ty: ts_type,
|
||||
}
|
||||
}
|
||||
|
||||
fn ts_keyword_type(kind: TsKeywordTypeKind) -> Box<TsType> {
|
||||
Box::new(TsType::TsKeywordType(TsKeywordType {
|
||||
span: DUMMY_SP,
|
||||
kind,
|
||||
}))
|
||||
}
|
||||
|
||||
fn ts_lit_type(lit: TsLit) -> Box<TsType> {
|
||||
Box::new(TsType::TsLitType(TsLitType {
|
||||
lit,
|
||||
span: DUMMY_SP,
|
||||
}))
|
||||
}
|
||||
|
||||
fn regex_type() -> Box<TsType> {
|
||||
Box::new(TsType::TsTypeRef(type_ref("RegExp".into())))
|
||||
}
|
||||
|
||||
fn maybe_lit_to_ts_type_const(lit: &Lit) -> Option<Box<TsType>> {
|
||||
match lit {
|
||||
Lit::Str(lit_str) => Some(ts_lit_type(TsLit::Str(lit_str.clone()))),
|
||||
Lit::Bool(lit_bool) => Some(ts_lit_type(TsLit::Bool(*lit_bool))),
|
||||
Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)),
|
||||
Lit::Num(lit_num) => Some(ts_lit_type(TsLit::Number(lit_num.clone()))),
|
||||
Lit::BigInt(lit_bigint) => Some(ts_lit_type(TsLit::BigInt(lit_bigint.clone()))),
|
||||
Lit::Regex(_) => Some(regex_type()),
|
||||
Lit::JSXText(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_lit_to_ts_type(lit: &Lit) -> Option<Box<TsType>> {
|
||||
match lit {
|
||||
Lit::Str(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)),
|
||||
Lit::Bool(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword)),
|
||||
Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)),
|
||||
Lit::Num(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword)),
|
||||
Lit::BigInt(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword)),
|
||||
Lit::Regex(_) => Some(regex_type()),
|
||||
Lit::JSXText(_) => None,
|
||||
}
|
||||
}
|
@ -1,14 +1,4 @@
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
#![allow(clippy::boxed_local)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
pub mod diagnostic;
|
||||
pub mod fast_dts;
|
||||
|
501
crates/swc_typescript/tests/fast_dts_deno.rs
Normal file
501
crates/swc_typescript/tests/fast_dts_deno.rs
Normal file
@ -0,0 +1,501 @@
|
||||
//! Tests copied from deno
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use swc_ecma_ast::EsVersion;
|
||||
use swc_ecma_codegen::to_code;
|
||||
use swc_ecma_parser::{parse_file_as_module, Syntax, TsConfig};
|
||||
use swc_typescript::fast_dts::FastDts;
|
||||
|
||||
#[track_caller]
|
||||
fn transform_dts_test(source: &str, expected: &str) {
|
||||
testing::run_test(false, |cm, _| {
|
||||
let fm = cm.new_source_file(
|
||||
swc_common::FileName::Real("test.ts".into()),
|
||||
source.to_string(),
|
||||
);
|
||||
|
||||
let mut checker = FastDts::new(Arc::new(fm.name.clone()));
|
||||
|
||||
let mut module = parse_file_as_module(
|
||||
&fm,
|
||||
Syntax::Typescript(TsConfig {
|
||||
..Default::default()
|
||||
}),
|
||||
EsVersion::latest(),
|
||||
None,
|
||||
&mut vec![],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _issues = checker.transform(&mut module);
|
||||
|
||||
let code = to_code(&module);
|
||||
|
||||
assert_eq!(
|
||||
code.trim(),
|
||||
expected.trim(),
|
||||
"Actual:\n{code}\nExpected:\n{expected}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_function_test() {
|
||||
transform_dts_test(
|
||||
r#"export function foo(a: number): number {
|
||||
return {};
|
||||
}"#,
|
||||
"export declare function foo(a: number): number;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export function foo(a: string): number;
|
||||
export function foo(a: any): number {
|
||||
return {};
|
||||
}"#,
|
||||
r#"export declare function foo(a: string): number;"#,
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export function foo(a = 2): number {
|
||||
return 2;
|
||||
}"#,
|
||||
r#"export declare function foo(a?: number): number;"#,
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export function foo(a: string = 2): number {
|
||||
return 2;
|
||||
}"#,
|
||||
r#"export declare function foo(a?: string): number;"#,
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export function foo([a, b] = [1, 2]): number {
|
||||
return 2;
|
||||
}"#,
|
||||
r#"export declare function foo([a, b]?: [number, number]): number;"#,
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export function foo({a, b} = { a: 1, b: 2 }): number {
|
||||
return 2;
|
||||
}"#,
|
||||
r#"export declare function foo({ a, b }?: {
|
||||
a: number;
|
||||
b: number;
|
||||
}): number;"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_class_decl_test() {
|
||||
transform_dts_test(
|
||||
r#"export class Foo {
|
||||
a: number = 2;
|
||||
static b: number = 1;
|
||||
#b: number = 3;
|
||||
constructor(value: string) {
|
||||
return 42;
|
||||
}
|
||||
foo(): string {
|
||||
return "abc";
|
||||
}
|
||||
#bar(): number {
|
||||
return 2
|
||||
}
|
||||
get asdf(): number {
|
||||
|
||||
}
|
||||
set asdf(value: number) {
|
||||
|
||||
}
|
||||
|
||||
static {
|
||||
|
||||
}
|
||||
}"#,
|
||||
r#"export declare class Foo {
|
||||
a: number;
|
||||
static b: number;
|
||||
constructor(value: string);
|
||||
foo(): string;
|
||||
get asdf(): number;
|
||||
set asdf(value: number);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_class_decl_rest_test() {
|
||||
transform_dts_test(
|
||||
r#"export class Foo {
|
||||
constructor(...args: string[]) {}
|
||||
}"#,
|
||||
r#"export declare class Foo {
|
||||
constructor(...args: string[]);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_class_decl_overloads_test() {
|
||||
transform_dts_test(
|
||||
r#"export class Foo {
|
||||
constructor(arg: string);
|
||||
constructor(arg: number);
|
||||
constructor(arg: any) {}
|
||||
}"#,
|
||||
r#"export declare class Foo {
|
||||
constructor(arg: string);
|
||||
constructor(arg: number);
|
||||
}"#,
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export class Foo {
|
||||
foo(arg: string);
|
||||
foo(arg: number);
|
||||
foo(arg: any) {}
|
||||
}"#,
|
||||
r#"export declare class Foo {
|
||||
foo(arg: string);
|
||||
foo(arg: number);
|
||||
}"#,
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export class Foo {
|
||||
constructor(arg: string);
|
||||
constructor(arg: number);
|
||||
constructor(arg: any) {}
|
||||
|
||||
bar(arg: number): number {
|
||||
return 2
|
||||
}
|
||||
|
||||
foo(arg: string);
|
||||
foo(arg: number);
|
||||
foo(arg: any) {}
|
||||
}"#,
|
||||
r#"export declare class Foo {
|
||||
constructor(arg: string);
|
||||
constructor(arg: number);
|
||||
bar(arg: number): number;
|
||||
foo(arg: string);
|
||||
foo(arg: number);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_class_decl_prop_test() {
|
||||
transform_dts_test(
|
||||
r#"export class Foo { private a!: string }"#,
|
||||
r#"export declare class Foo {
|
||||
private a: string;
|
||||
}"#,
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export class Foo { declare a: string }"#,
|
||||
r#"export declare class Foo {
|
||||
a: string;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_class_decl_prop_infer_test() {
|
||||
transform_dts_test(
|
||||
r#"export class Foo { foo = (a: string): string => ({} as any) }"#,
|
||||
r#"export declare class Foo {
|
||||
foo: (a: string) => string;
|
||||
}"#,
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export class Foo { foo = function(a: string): void {} }"#,
|
||||
r#"export declare class Foo {
|
||||
foo: (a: string) => void;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_var_decl_test() {
|
||||
transform_dts_test(
|
||||
r#"export const foo: number = 42;"#,
|
||||
"export declare const foo: number;",
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export var foo: number = 42;"#,
|
||||
"export declare var foo: number;",
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export let foo: number = 42;"#,
|
||||
"export declare let foo: number;",
|
||||
);
|
||||
|
||||
// Default to any if it cannot be determined
|
||||
transform_dts_test(
|
||||
r#"export const foo = adsf.b;"#,
|
||||
"export declare const foo: any;",
|
||||
);
|
||||
transform_dts_test(r#"export let foo;"#, "export declare let foo: any;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_global_declare() {
|
||||
transform_dts_test(
|
||||
r#"declare global {
|
||||
interface String {
|
||||
fancyFormat(opts: StringFormatOptions): string;
|
||||
}
|
||||
}"#,
|
||||
r#"declare global {
|
||||
interface String {
|
||||
fancyFormat(opts: StringFormatOptions): string;
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_inference() {
|
||||
transform_dts_test(
|
||||
r#"export const foo = null as string as number;"#,
|
||||
"export declare const foo: number;",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_as_const() {
|
||||
transform_dts_test(
|
||||
r#"export const foo = [1, 2] as const;"#,
|
||||
"export declare const foo: readonly [1, 2];",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export const foo = [1, ,2] as const;"#,
|
||||
"export declare const foo: readonly [1, any, 2];",
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export const foo = { str: "bar", bool: true, bool2: false, num: 42, nullish: null } as const;"#,
|
||||
r#"export declare const foo: {
|
||||
readonly str: "bar";
|
||||
readonly bool: true;
|
||||
readonly bool2: false;
|
||||
readonly num: 42;
|
||||
readonly nullish: null;
|
||||
};"#,
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export const foo = { str: [1, 2] as const } as const;"#,
|
||||
r#"export declare const foo: {
|
||||
readonly str: readonly [1, 2];
|
||||
};"#,
|
||||
);
|
||||
|
||||
// TODO: Requires type resolving
|
||||
transform_dts_test(
|
||||
r#"export const foo = { bar } as const;"#,
|
||||
r#"export declare const foo: {
|
||||
};"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_literal_inference_ann() {
|
||||
transform_dts_test(
|
||||
r#"export const foo: number = "abc";"#,
|
||||
"export declare const foo: number;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo: number = "abc";"#,
|
||||
"export declare let foo: number;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export var foo: number = "abc";"#,
|
||||
"export declare var foo: number;",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_literal_inference() {
|
||||
transform_dts_test(
|
||||
r#"export const foo = 42;"#,
|
||||
"export declare const foo: number;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export const foo = "foo";"#,
|
||||
"export declare const foo: string;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export const foo = true;"#,
|
||||
"export declare const foo: boolean;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export const foo = false;"#,
|
||||
"export declare const foo: boolean;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export const foo = null;"#,
|
||||
"export declare const foo: null;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = undefined;"#,
|
||||
"export declare let foo: any;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = 10n;"#,
|
||||
"export declare let foo: bigint;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = /foo/;"#,
|
||||
"export declare let foo: RegExp;",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_fn_expr() {
|
||||
transform_dts_test(
|
||||
r#"export let foo = function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}"#,
|
||||
"export declare let foo: (a: number, b: number) => number;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = function add<T>([a, b]: T): void {}"#,
|
||||
"export declare let foo: <T>([a, b]: T) => void;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = function add<T>({a, b}: T): void {}"#,
|
||||
"export declare let foo: <T>({ a, b }: T) => void;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = function add(a = 2): void {}"#,
|
||||
"export declare let foo: (a: number) => void;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = function add(...params: any[]): void {}"#,
|
||||
"export declare let foo: (...params: any[]) => void;",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_fn_arrow_expr() {
|
||||
transform_dts_test(
|
||||
r#"export let foo = (a: number, b: number): number => {
|
||||
return a + b;
|
||||
}"#,
|
||||
"export declare let foo: (a: number, b: number) => number;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = <T>([a, b]: T): void => {}"#,
|
||||
"export declare let foo: <T>([a, b]: T) => void;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = <T>({a, b}: T): void => {}"#,
|
||||
"export declare let foo: <T>({ a, b }: T) => void;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export let foo = (a = 2): void => {}"#,
|
||||
"export declare let foo: (a: number) => void;",
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export let foo = (...params: any[]): void => {}"#,
|
||||
"export declare let foo: (...params: any[]) => void;",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_type_export() {
|
||||
transform_dts_test(r#"interface Foo {}"#, "interface Foo {\n}");
|
||||
transform_dts_test(r#"type Foo = number;"#, "type Foo = number;");
|
||||
|
||||
transform_dts_test(r#"export interface Foo {}"#, "export interface Foo {\n}");
|
||||
transform_dts_test(r#"export type Foo = number;"#, "export type Foo = number;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_enum_export() {
|
||||
transform_dts_test(
|
||||
r#"export enum Foo { A, B }"#,
|
||||
"export declare enum Foo {\n A,\n B\n}",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export const enum Foo { A, B }"#,
|
||||
"export declare const enum Foo {\n A,\n B\n}",
|
||||
);
|
||||
|
||||
transform_dts_test(
|
||||
r#"export enum Foo { A = "foo", B = "bar" }"#,
|
||||
r#"export declare enum Foo {
|
||||
A = "foo",
|
||||
B = "bar"
|
||||
}"#,
|
||||
);
|
||||
|
||||
// TODO: Enum rules https://www.typescriptlang.org/docs/handbook/enums.html
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_default_export() {
|
||||
transform_dts_test(
|
||||
r#"export default function(a: number, b: number): number {};"#,
|
||||
"export default function(a: number, b: number): number;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export default function(a: number, b: number): number;
|
||||
export default function(a: number, b: number): any {
|
||||
return foo
|
||||
};"#,
|
||||
"export default function(a: number, b: number): number;",
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export default class {foo = 2};"#,
|
||||
r#"export default class {
|
||||
foo: number;
|
||||
}"#,
|
||||
);
|
||||
transform_dts_test(
|
||||
r#"export default 42;"#,
|
||||
r#"declare const _dts_1: number;
|
||||
export default _dts_1;"#,
|
||||
);
|
||||
// TODO
|
||||
// transform_dts_test(
|
||||
// r#"const a: number = 42; export default a;"#,
|
||||
// r#"declare const a: number;
|
||||
// export default a;"#,
|
||||
// )
|
||||
// ;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_default_export_named() {
|
||||
transform_dts_test(
|
||||
r#"export { foo, bar } from "foo";"#,
|
||||
r#"export { foo, bar } from "foo";"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_default_export_all() {
|
||||
transform_dts_test(
|
||||
r#"export * as foo from "foo";"#,
|
||||
r#"export * as foo from "foo";"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dts_imports() {
|
||||
transform_dts_test(
|
||||
r#"import { foo } from "bar";export const foobar = foo;"#,
|
||||
r#"import { foo } from "bar";
|
||||
export declare const foobar: any;"#,
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user