From 0adad25da123875c8cec2759004d8264237688f0 Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Mon, 2 Dec 2024 09:38:36 +0800 Subject: [PATCH] feat(typescript): Align `isolatedDeclaration` implementation with tsc (#9715) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Description:** - [x] **Basic implementation.** This also includes diagnostics and type inference. - [x] Function - [x] Class - [x] Variable declaration - [x] Enum - [x] **Remove unused imports and declarations.** Based on the result ast of fast dts transformation, we collect the usage of ids which also contain syntax context. Then we prune the ast to cut off the unused imports and declarations. - [x] **Port tests and bug fixes.** To ensure correctness, I'd like to port the fixtures as oxc does https://github.com/oxc-project/oxc/tree/main/crates/oxc_isolated_declarations/tests/fixtures. - [x] **Benmarks** - [x] Strip internal **Implementation:** Most code is direct translation of https://github.com/oxc-project/oxc/tree/main/crates/oxc_isolated_declarations. The differences come from: 1. swc and oxc use **different ast**. 2. The transformation of swc is **mutation-based**, which directly mutates the whole cloned ast, while the transformation of oxc is **immutation-based**, which constructs and copies the ast nodes on demand. Maybe mutation-based transformation is not better, but I think it's also annoyed to construct ast nodes and it could be easy to refactor if there is some demands in the future. 3. oxc transforms while collecting references information to prune unused idents. However, the ast nodes of swc contain **syntax context**, we don't need to collect the information again. So I build a reference graph and calculate the reachability of identifiers. 4. I think oxc could have bugs. I manually compare the result with `tsc --declaration --isolatedDeclarations --emitDeclarationOnly --isolatedModules --noResolve --noCheck --strictNullChecks test.ts`. I also reguard those cases where tsc can't compile as undefined behaviors. 😅 Actually I find my implementation of point 2 is not too good either. I meet many bad cases after my basic implementation and write many ugly patches. So issues are welcome. **Benchmarks:** Roughly test with https://raw.githubusercontent.com/oxc-project/benchmark-files/main/vue-id.ts under M3Pro. | | parse without syntax context resolution | isolated declarations | | -------------- | ------ | --------------------------------- | | swc | 282.68ms | 335.44ms | | oxc | 53.286ms | 63.917ms | | tsc | - | 3.875s | **Known problem:** - Due to the comment emitting algorithm in swc, isolated declaration may also generate comments with wrong positions. **Related issue:** - Closes https://github.com/swc-project/swc/issues/9705 - Closes https://github.com/swc-project/swc/issues/9718 --- .changeset/tiny-snakes-train.md | 5 + Cargo.lock | 5 + crates/swc/Cargo.toml | 5 + crates/swc/benches/assets/vue-id.ts | 503186 +++++++++++++++ crates/swc/benches/isolated_declarations.rs | 50 + crates/swc/src/lib.rs | 6 +- crates/swc/tests/projects.rs | 2 +- .../ts-isolated-declaration/error/.swcrc | 10 - .../issues/input/.swcrc | 11 - .../issues/input/swc-issue-9550.ts | 9 - .../ts-isolated-declaration/oxc/README.md | 3 - .../ts-isolated-declaration/oxc/input/.swcrc | 11 - .../oxc/input/arrow-function-return-type.ts | 9 - .../oxc/input/as-const.swc-stderr | 14 - .../oxc/input/empty-export.ts | 4 - .../oxc/input/function-overloads.swc-stderr | 10 - .../oxc/input/generator.ts | 10 - .../oxc/input/infer-expression.swc-stderr | 27 - .../oxc/input/infer-expression.ts | 5 - .../input/infer-template-literal.swc-stderr | 24 - .../non-exported-binding-elements.swc-stderr | 21 - .../output/arrow-function-return-type.d.ts | 3 - .../oxc/output/arrow-function-return-type.ts | 9 - .../oxc/output/as-const.d.ts | 17 - .../oxc/output/as-const.ts | 22 - .../oxc/output/async-function.d.ts | 7 - .../oxc/output/async-function.ts | 58 - .../oxc/output/eliminate-imports.ts | 19 - .../oxc/output/empty-export.d.ts | 2 - .../oxc/output/empty-export.ts | 3 - .../oxc/output/function-overloads.ts | 3 - .../oxc/output/function-parameters.d.ts | 17 - .../oxc/output/function-parameters.ts | 46 - .../oxc/output/generator.d.ts | 5 - .../oxc/output/generator.ts | 28 - .../oxc/output/infer-expression.d.ts | 5 - .../oxc/output/infer-expression.ts | 5 - .../oxc/output/infer-return-type.d.ts | 7 - .../oxc/output/infer-return-type.ts | 24 - .../oxc/output/infer-template-literal.d.ts | 7 - .../oxc/output/infer-template-literal.ts | 11 - .../output/non-exported-binding-elements.d.ts | 7 - .../output/non-exported-binding-elements.ts | 16 - .../oxc/output/readonly.ts | 2 - .../input/src/example.ts | 2 - .../output/src/example.d.ts | 2 - .../output/src/example.ts | 2 - .../ts-isolated-declaration/simple/.swcrc | 10 - .../ts-isolated-declaration/simple/input/1.ts | 4 - .../ts-isolated-declaration/simple/input/2.ts | 1 - .../ts-isolated-declaration/simple/input/3.ts | 1 - .../simple/output/1.d.ts | 1 - .../simple/output/1.ts | 1 - .../simple/output/2.d.ts | 1 - .../simple/output/2.ts | 1 - .../simple/output/3.d.ts | 4 - .../simple/output/3.ts | 5 - .../rewrite-import-specifier/input/.swcrc | 0 .../rewrite-import-specifier/input/index.ts | 0 .../output/index.d.ts | 0 .../rewrite-import-specifier/output/index.ts | 0 crates/swc_typescript/Cargo.toml | 19 +- .../examples/isolated_declarations.rs | 63 + crates/swc_typescript/src/diagnostic.rs | 193 +- crates/swc_typescript/src/fast_dts/class.rs | 535 + crates/swc_typescript/src/fast_dts/decl.rs | 204 + crates/swc_typescript/src/fast_dts/enum.rs | 217 + .../swc_typescript/src/fast_dts/function.rs | 257 + .../swc_typescript/src/fast_dts/inferrer.rs | 200 + crates/swc_typescript/src/fast_dts/mod.rs | 1382 +- crates/swc_typescript/src/fast_dts/types.rs | 337 + .../src/fast_dts/util/ast_ext.rs | 119 + .../util/expando_function_collector.rs | 53 + .../swc_typescript/src/fast_dts/util/mod.rs | 3 + .../swc_typescript/src/fast_dts/util/types.rs | 27 + .../fast_dts/visitors/internal_annotation.rs | 22 + .../src/fast_dts/visitors/mod.rs | 2 + .../src/fast_dts/visitors/type_usage.rs | 273 + .../tests/{fast_dts_deno.rs => deno_test.rs} | 176 +- .../tests/fixture/abstract-overloads.snap | 13 + .../tests/fixture/abstract-overloads.ts | 13 + .../tests/fixture/binding-ref.snap | 8 + .../tests/fixture/binding-ref.ts | 3 + .../tests/fixture/class-abstract-method.snap | 9 + .../tests/fixture/class-abstract-method.ts | 9 + .../fixture/class-params-initializers.snap | 27 + .../fixture/class-params-initializers.ts | 24 + .../fixture/class-properties-computed.snap | 32 + .../fixture/class-properties-computed.ts | 6 + .../tests/fixture/issues/9550.snap} | 11 +- .../tests/fixture/issues/9550.ts} | 3 +- .../tests/fixture/simple-constants.snap | 10 + .../tests/fixture/simple-constants.ts | 6 + .../tests/fixture/transitive-references.snap | 7 + .../tests/fixture/transitive-references.ts | 9 + .../tests/fixture/ts-property.snap | 11 + .../tests/fixture/ts-property.ts | 6 + .../arrow-function-return-type.snap | 32 + .../oxc_fixture/arrow-function-return-type.ts | 15 + .../tests/oxc_fixture/as-const.snap | 22 + .../tests/oxc_fixture}/as-const.ts | 2 +- .../tests/oxc_fixture/async-function.snap | 42 + .../tests/oxc_fixture}/async-function.ts | 14 +- .../tests/oxc_fixture/class.snap | 65 + .../swc_typescript/tests/oxc_fixture/class.ts | 71 + .../tests/oxc_fixture/declare-global.snap | 11 + .../tests/oxc_fixture/declare-global.ts | 11 + .../tests/oxc_fixture/eliminate-imports.snap} | 9 +- .../tests/oxc_fixture}/eliminate-imports.ts | 8 +- .../tests/oxc_fixture/empty-export.snap | 11 + .../tests/oxc_fixture/empty-export.ts | 8 + .../tests/oxc_fixture/empty-export2.snap | 5 + .../tests/oxc_fixture/empty-export2.ts | 1 + .../tests/oxc_fixture/expando-function.snap | 47 + .../tests/oxc_fixture/expando-function.ts | 34 + .../tests/oxc_fixture/export-default.snap | 7 + .../tests/oxc_fixture/export-default.ts | 6 + .../oxc_fixture/function-overloads.snap} | 4 + .../tests/oxc_fixture}/function-overloads.ts | 2 +- .../oxc_fixture/function-parameters.snap | 67 + .../tests/oxc_fixture}/function-parameters.ts | 12 +- .../oxc_fixture/function-signatures.snap | 11 + .../tests/oxc_fixture/function-signatures.ts | 24 + .../tests/oxc_fixture/generator.snap | 33 + .../tests/oxc_fixture/generator.ts | 25 + .../tests/oxc_fixture/infer-expression.snap | 52 + .../tests/oxc_fixture/infer-expression.ts | 19 + .../tests/oxc_fixture/infer-return-type.snap | 25 + .../tests/oxc_fixture}/infer-return-type.ts | 15 +- .../oxc_fixture/infer-template-literal.snap | 29 + .../oxc_fixture}/infer-template-literal.ts | 6 +- .../tests/oxc_fixture/mapped-types.snap | 11 + .../tests/oxc_fixture/mapped-types.ts | 6 + .../module-declaration-with-export.snap | 31 + .../module-declaration-with-export.ts | 36 + .../tests/oxc_fixture/module-declaration.snap | 24 + .../tests/oxc_fixture/module-declaration.ts | 31 + .../non-exported-binding-elements.snap | 48 + .../non-exported-binding-elements.ts | 4 +- .../tests/oxc_fixture/readonly.snap} | 4 + .../tests/oxc_fixture}/readonly.ts | 0 .../tests/oxc_fixture/set-get-accessor.snap | 29 + .../tests/oxc_fixture/set-get-accessor.ts | 26 + .../tests/oxc_fixture/signatures.snap | 30 + .../tests/oxc_fixture/signatures.ts | 27 + .../tests/oxc_fixture/strip-internal.snap | 16 + .../tests/oxc_fixture/strip-internal.ts | 82 + .../oxc_fixture/ts-export-assignment.snap | 7 + .../tests/oxc_fixture/ts-export-assignment.ts | 5 + crates/swc_typescript/tests/typescript.rs | 67 + 150 files changed, 507614 insertions(+), 1689 deletions(-) create mode 100644 .changeset/tiny-snakes-train.md create mode 100644 crates/swc/benches/assets/vue-id.ts create mode 100644 crates/swc/benches/isolated_declarations.rs delete mode 100644 crates/swc/tests/ts-isolated-declaration/error/.swcrc delete mode 100644 crates/swc/tests/ts-isolated-declaration/issues/input/.swcrc delete mode 100644 crates/swc/tests/ts-isolated-declaration/issues/input/swc-issue-9550.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/README.md delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/.swcrc delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/arrow-function-return-type.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/as-const.swc-stderr delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/empty-export.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/function-overloads.swc-stderr delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/generator.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/infer-expression.swc-stderr delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/infer-expression.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/infer-template-literal.swc-stderr delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/input/non-exported-binding-elements.swc-stderr delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/arrow-function-return-type.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/arrow-function-return-type.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/as-const.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/as-const.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/async-function.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/async-function.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/eliminate-imports.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/empty-export.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/empty-export.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/function-overloads.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/function-parameters.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/function-parameters.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/generator.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/generator.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/infer-expression.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/infer-expression.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/infer-return-type.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/infer-return-type.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/infer-template-literal.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/infer-template-literal.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/non-exported-binding-elements.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/non-exported-binding-elements.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/oxc/output/readonly.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/rewrite-import-specifier/input/src/example.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/rewrite-import-specifier/output/src/example.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/rewrite-import-specifier/output/src/example.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/.swcrc delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/input/1.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/input/2.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/input/3.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/output/1.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/output/1.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/output/2.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/output/2.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/output/3.d.ts delete mode 100644 crates/swc/tests/ts-isolated-declaration/simple/output/3.ts rename crates/swc/tests/{ts-isolated-declaration => typescript}/rewrite-import-specifier/input/.swcrc (100%) rename crates/swc/tests/{ts-isolated-declaration => typescript}/rewrite-import-specifier/input/index.ts (100%) rename crates/swc/tests/{ts-isolated-declaration => typescript}/rewrite-import-specifier/output/index.d.ts (100%) rename crates/swc/tests/{ts-isolated-declaration => typescript}/rewrite-import-specifier/output/index.ts (100%) create mode 100644 crates/swc_typescript/examples/isolated_declarations.rs create mode 100644 crates/swc_typescript/src/fast_dts/class.rs create mode 100644 crates/swc_typescript/src/fast_dts/decl.rs create mode 100644 crates/swc_typescript/src/fast_dts/enum.rs create mode 100644 crates/swc_typescript/src/fast_dts/function.rs create mode 100644 crates/swc_typescript/src/fast_dts/inferrer.rs create mode 100644 crates/swc_typescript/src/fast_dts/types.rs create mode 100644 crates/swc_typescript/src/fast_dts/util/ast_ext.rs create mode 100644 crates/swc_typescript/src/fast_dts/util/expando_function_collector.rs create mode 100644 crates/swc_typescript/src/fast_dts/util/mod.rs create mode 100644 crates/swc_typescript/src/fast_dts/util/types.rs create mode 100644 crates/swc_typescript/src/fast_dts/visitors/internal_annotation.rs create mode 100644 crates/swc_typescript/src/fast_dts/visitors/mod.rs create mode 100644 crates/swc_typescript/src/fast_dts/visitors/type_usage.rs rename crates/swc_typescript/tests/{fast_dts_deno.rs => deno_test.rs} (72%) create mode 100644 crates/swc_typescript/tests/fixture/abstract-overloads.snap create mode 100644 crates/swc_typescript/tests/fixture/abstract-overloads.ts create mode 100644 crates/swc_typescript/tests/fixture/binding-ref.snap create mode 100644 crates/swc_typescript/tests/fixture/binding-ref.ts create mode 100644 crates/swc_typescript/tests/fixture/class-abstract-method.snap create mode 100644 crates/swc_typescript/tests/fixture/class-abstract-method.ts create mode 100644 crates/swc_typescript/tests/fixture/class-params-initializers.snap create mode 100644 crates/swc_typescript/tests/fixture/class-params-initializers.ts create mode 100644 crates/swc_typescript/tests/fixture/class-properties-computed.snap create mode 100644 crates/swc_typescript/tests/fixture/class-properties-computed.ts rename crates/{swc/tests/ts-isolated-declaration/issues/output/swc-issue-9550.ts => swc_typescript/tests/fixture/issues/9550.snap} (56%) rename crates/{swc/tests/ts-isolated-declaration/issues/output/swc-issue-9550.d.ts => swc_typescript/tests/fixture/issues/9550.ts} (73%) create mode 100644 crates/swc_typescript/tests/fixture/simple-constants.snap create mode 100644 crates/swc_typescript/tests/fixture/simple-constants.ts create mode 100644 crates/swc_typescript/tests/fixture/transitive-references.snap create mode 100644 crates/swc_typescript/tests/fixture/transitive-references.ts create mode 100644 crates/swc_typescript/tests/fixture/ts-property.snap create mode 100644 crates/swc_typescript/tests/fixture/ts-property.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/arrow-function-return-type.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/arrow-function-return-type.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/as-const.snap rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/as-const.ts (96%) create mode 100644 crates/swc_typescript/tests/oxc_fixture/async-function.snap rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/async-function.ts (70%) create mode 100644 crates/swc_typescript/tests/oxc_fixture/class.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/class.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/declare-global.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/declare-global.ts rename crates/{swc/tests/ts-isolated-declaration/oxc/output/eliminate-imports.d.ts => swc_typescript/tests/oxc_fixture/eliminate-imports.snap} (55%) rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/eliminate-imports.ts (56%) create mode 100644 crates/swc_typescript/tests/oxc_fixture/empty-export.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/empty-export.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/empty-export2.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/empty-export2.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/expando-function.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/expando-function.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/export-default.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/export-default.ts rename crates/{swc/tests/ts-isolated-declaration/oxc/output/function-overloads.d.ts => swc_typescript/tests/oxc_fixture/function-overloads.snap} (74%) rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/function-overloads.ts (78%) create mode 100644 crates/swc_typescript/tests/oxc_fixture/function-parameters.snap rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/function-parameters.ts (67%) create mode 100644 crates/swc_typescript/tests/oxc_fixture/function-signatures.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/function-signatures.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/generator.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/generator.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/infer-expression.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/infer-expression.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/infer-return-type.snap rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/infer-return-type.ts (63%) create mode 100644 crates/swc_typescript/tests/oxc_fixture/infer-template-literal.snap rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/infer-template-literal.ts (55%) create mode 100644 crates/swc_typescript/tests/oxc_fixture/mapped-types.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/mapped-types.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/module-declaration-with-export.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/module-declaration-with-export.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/module-declaration.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/module-declaration.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/non-exported-binding-elements.snap rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/non-exported-binding-elements.ts (71%) rename crates/{swc/tests/ts-isolated-declaration/oxc/output/readonly.d.ts => swc_typescript/tests/oxc_fixture/readonly.snap} (68%) rename crates/{swc/tests/ts-isolated-declaration/oxc/input => swc_typescript/tests/oxc_fixture}/readonly.ts (100%) create mode 100644 crates/swc_typescript/tests/oxc_fixture/set-get-accessor.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/set-get-accessor.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/signatures.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/signatures.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/strip-internal.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/strip-internal.ts create mode 100644 crates/swc_typescript/tests/oxc_fixture/ts-export-assignment.snap create mode 100644 crates/swc_typescript/tests/oxc_fixture/ts-export-assignment.ts create mode 100644 crates/swc_typescript/tests/typescript.rs diff --git a/.changeset/tiny-snakes-train.md b/.changeset/tiny-snakes-train.md new file mode 100644 index 000000000000..b2e3b6c03371 --- /dev/null +++ b/.changeset/tiny-snakes-train.md @@ -0,0 +1,5 @@ +--- +swc_typescript: major +--- + +feat(typescript): Align `isolatedDeclaration` implementation with tsc diff --git a/Cargo.lock b/Cargo.lock index f36eafeb52f2..3c027fe54037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5868,11 +5868,16 @@ dependencies = [ name = "swc_typescript" version = "4.0.0" dependencies = [ + "petgraph", + "rustc-hash", "swc_atoms", "swc_common", "swc_ecma_ast", "swc_ecma_codegen", "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", "testing", "thiserror", ] diff --git a/crates/swc/Cargo.toml b/crates/swc/Cargo.toml index 628033ef34dd..156d50c0e63e 100644 --- a/crates/swc/Cargo.toml +++ b/crates/swc/Cargo.toml @@ -166,3 +166,8 @@ name = "oxc" [[bench]] harness = false name = "typescript" + + +[[bench]] +harness = false +name = "isolated_declarations" diff --git a/crates/swc/benches/assets/vue-id.ts b/crates/swc/benches/assets/vue-id.ts new file mode 100644 index 000000000000..3f1450431551 --- /dev/null +++ b/crates/swc/benches/assets/vue-id.ts @@ -0,0 +1,503186 @@ +// All vue files combined. + +import { type VNode, type VNodeChild, isVNode } from './vnode' +import { + EffectScope, + type ReactiveEffect, + TrackOpTypes, + isRef, + markRaw, + pauseTracking, + proxyRefs, + resetTracking, + shallowReadonly, + track, +} from '@vue/reactivity' +import { + type ComponentPublicInstance, + type ComponentPublicInstanceConstructor, + PublicInstanceProxyHandlers, + RuntimeCompiledPublicInstanceProxyHandlers, + createDevRenderContext, + exposePropsOnRenderContext, + exposeSetupStateOnRenderContext, + publicPropertiesMap, +} from './componentPublicInstance' +import { + type ComponentPropsOptions, + type NormalizedPropsOptions, + initProps, + normalizePropsOptions, +} from './componentProps' +import { + type InternalSlots, + type Slots, + type SlotsType, + type UnwrapSlotsType, + initSlots, +} from './componentSlots' +import { warn } from './warning' +import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling' +import { + type AppConfig, + type AppContext, + createAppContext, +} from './apiCreateApp' +import { type Directive, validateDirectiveName } from './directives' +import { + type ComponentOptions, + type ComputedOptions, + type MergedComponentOptions, + type MethodOptions, + applyOptions, + resolveMergedOptions, +} from './componentOptions' +import { + type EmitFn, + type EmitsOptions, + type EmitsToProps, + type ObjectEmitsOptions, + type ShortEmitsToObject, + emit, + normalizeEmitsOptions, +} from './componentEmits' +import { + EMPTY_OBJ, + type IfAny, + NOOP, + ShapeFlags, + extend, + getGlobalThis, + isArray, + isFunction, + isObject, + isPromise, + makeMap, +} from '@vue/shared' +import type { SuspenseBoundary } from './components/Suspense' +import type { CompilerOptions } from '@vue/compiler-core' +import { markAttrsAccessed } from './componentRenderUtils' +import { currentRenderingInstance } from './componentRenderContext' +import { endMeasure, startMeasure } from './profiling' +import { convertLegacyRenderFn } from './compat/renderFn' +import { + type CompatConfig, + globalCompatConfig, + validateCompatConfig, +} from './compat/compatConfig' +import type { SchedulerJob } from './scheduler' +import type { LifecycleHooks } from './enums' + +export type Data = Record + +/** + * Public utility type for extracting the instance type of a component. + * Works with all valid component definition types. This is intended to replace + * the usage of `InstanceType` which only works for + * constructor-based component definition types. + * + * Exmaple: + * ```ts + * const MyComp = { ... } + * declare const instance: ComponentInstance + * ``` + */ +export type ComponentInstance = T extends { new (): ComponentPublicInstance } + ? InstanceType + : T extends FunctionalComponent + ? ComponentPublicInstance> + : T extends Component< + infer Props, + infer RawBindings, + infer D, + infer C, + infer M + > + ? // NOTE we override Props/RawBindings/D to make sure is not `unknown` + ComponentPublicInstance< + unknown extends Props ? {} : Props, + unknown extends RawBindings ? {} : RawBindings, + unknown extends D ? {} : D, + C, + M + > + : never // not a vue Component + +/** + * For extending allowed non-declared props on components in TSX + */ +export interface ComponentCustomProps {} + +/** + * Default allowed non-declared props on component in TSX + */ +export interface AllowedComponentProps { + class?: unknown + style?: unknown +} + +// Note: can't mark this whole interface internal because some public interfaces +// extend it. +export interface ComponentInternalOptions { + /** + * @internal + */ + __scopeId?: string + /** + * @internal + */ + __cssModules?: Data + /** + * @internal + */ + __hmrId?: string + /** + * Compat build only, for bailing out of certain compatibility behavior + */ + __isBuiltIn?: boolean + /** + * This one should be exposed so that devtools can make use of it + */ + __file?: string + /** + * name inferred from filename + */ + __name?: string +} + +export interface FunctionalComponent< + P = {}, + E extends EmitsOptions | Record = {}, + S extends Record = any, + EE extends EmitsOptions = ShortEmitsToObject, +> extends ComponentInternalOptions { + // use of any here is intentional so it can be a valid JSX Element constructor + ( + props: P & EmitsToProps, + ctx: Omit>>, 'expose'>, + ): any + props?: ComponentPropsOptions

+ emits?: EE | (keyof EE)[] + slots?: IfAny> + inheritAttrs?: boolean + displayName?: string + compatConfig?: CompatConfig +} + +export interface ClassComponent { + new (...args: any[]): ComponentPublicInstance + __vccOpts: ComponentOptions +} + +/** + * Concrete component type matches its actual value: it's either an options + * object, or a function. Use this where the code expects to work with actual + * values, e.g. checking if its a function or not. This is mostly for internal + * implementation code. + */ +export type ConcreteComponent< + Props = {}, + RawBindings = any, + D = any, + C extends ComputedOptions = ComputedOptions, + M extends MethodOptions = MethodOptions, + E extends EmitsOptions | Record = {}, + S extends Record = any, +> = + | ComponentOptions + | FunctionalComponent + +/** + * A type used in public APIs where a component type is expected. + * The constructor type is an artificial type returned by defineComponent(). + */ +export type Component< + Props = any, + RawBindings = any, + D = any, + C extends ComputedOptions = ComputedOptions, + M extends MethodOptions = MethodOptions, + E extends EmitsOptions | Record = {}, + S extends Record = any, +> = + | ConcreteComponent + | ComponentPublicInstanceConstructor + +export type { ComponentOptions } + +export type LifecycleHook = (TFn & SchedulerJob)[] | null + +// use `E extends any` to force evaluating type to fix #2362 +export type SetupContext< + E = EmitsOptions, + S extends SlotsType = {}, +> = E extends any + ? { + attrs: Data + slots: UnwrapSlotsType + emit: EmitFn + expose: = Record>( + exposed?: Exposed, + ) => void + } + : never + +/** + * @internal + */ +export type InternalRenderFunction = { + ( + ctx: ComponentPublicInstance, + cache: ComponentInternalInstance['renderCache'], + // for compiler-optimized bindings + $props: ComponentInternalInstance['props'], + $setup: ComponentInternalInstance['setupState'], + $data: ComponentInternalInstance['data'], + $options: ComponentInternalInstance['ctx'], + ): VNodeChild + _rc?: boolean // isRuntimeCompiled + + // __COMPAT__ only + _compatChecked?: boolean // v3 and already checked for v2 compat + _compatWrapped?: boolean // is wrapped for v2 compat +} + +/** + * We expose a subset of properties on the internal instance as they are + * useful for advanced external libraries and tools. + */ +export interface ComponentInternalInstance { + uid: number + type: ConcreteComponent + parent: ComponentInternalInstance | null + root: ComponentInternalInstance + appContext: AppContext + /** + * Vnode representing this component in its parent's vdom tree + */ + vnode: VNode + /** + * The pending new vnode from parent updates + * @internal + */ + next: VNode | null + /** + * Root vnode of this component's own vdom tree + */ + subTree: VNode + /** + * Render effect instance + */ + effect: ReactiveEffect + /** + * Bound effect runner to be passed to schedulers + */ + update: SchedulerJob + /** + * The render function that returns vdom tree. + * @internal + */ + render: InternalRenderFunction | null + /** + * SSR render function + * @internal + */ + ssrRender?: Function | null + /** + * Object containing values this component provides for its descendants + * @internal + */ + provides: Data + /** + * Tracking reactive effects (e.g. watchers) associated with this component + * so that they can be automatically stopped on component unmount + * @internal + */ + scope: EffectScope + /** + * cache for proxy access type to avoid hasOwnProperty calls + * @internal + */ + accessCache: Data | null + /** + * cache for render function values that rely on _ctx but won't need updates + * after initialized (e.g. inline handlers) + * @internal + */ + renderCache: (Function | VNode | undefined)[] + + /** + * Resolved component registry, only for components with mixins or extends + * @internal + */ + components: Record | null + /** + * Resolved directive registry, only for components with mixins or extends + * @internal + */ + directives: Record | null + /** + * Resolved filters registry, v2 compat only + * @internal + */ + filters?: Record + /** + * resolved props options + * @internal + */ + propsOptions: NormalizedPropsOptions + /** + * resolved emits options + * @internal + */ + emitsOptions: ObjectEmitsOptions | null + /** + * resolved inheritAttrs options + * @internal + */ + inheritAttrs?: boolean + /** + * is custom element? + * @internal + */ + isCE?: boolean + /** + * custom element specific HMR method + * @internal + */ + ceReload?: (newStyles?: string[]) => void + + // the rest are only for stateful components --------------------------------- + + // main proxy that serves as the public instance (`this`) + proxy: ComponentPublicInstance | null + + // exposed properties via expose() + exposed: Record | null + exposeProxy: Record | null + + /** + * alternative proxy used only for runtime-compiled render functions using + * `with` block + * @internal + */ + withProxy: ComponentPublicInstance | null + /** + * This is the target for the public instance proxy. It also holds properties + * injected by user options (computed, methods etc.) and user-attached + * custom properties (via `this.x = ...`) + * @internal + */ + ctx: Data + + // state + data: Data + props: Data + attrs: Data + slots: InternalSlots + refs: Data + emit: EmitFn + + attrsProxy: Data | null + slotsProxy: Slots | null + + /** + * used for keeping track of .once event handlers on components + * @internal + */ + emitted: Record | null + /** + * used for caching the value returned from props default factory functions to + * avoid unnecessary watcher trigger + * @internal + */ + propsDefaults: Data + /** + * setup related + * @internal + */ + setupState: Data + /** + * devtools access to additional info + * @internal + */ + devtoolsRawSetupState?: any + /** + * @internal + */ + setupContext: SetupContext | null + + /** + * suspense related + * @internal + */ + suspense: SuspenseBoundary | null + /** + * suspense pending batch id + * @internal + */ + suspenseId: number + /** + * @internal + */ + asyncDep: Promise | null + /** + * @internal + */ + asyncResolved: boolean + + // lifecycle + isMounted: boolean + isUnmounted: boolean + isDeactivated: boolean + /** + * @internal + */ + [LifecycleHooks.BEFORE_CREATE]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.CREATED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.MOUNTED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.UPDATED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.UNMOUNTED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.RENDER_TRACKED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.ACTIVATED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.DEACTIVATED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise> + + /** + * For caching bound $forceUpdate on public proxy access + * @internal + */ + f?: () => void + /** + * For caching bound $nextTick on public proxy access + * @internal + */ + n?: () => Promise + /** + * `updateTeleportCssVars` + * For updating css vars on contained teleports + * @internal + */ + ut?: (vars?: Record) => void + + /** + * dev only. For style v-bind hydration mismatch checks + * @internal + */ + getCssVars?: () => Record + + /** + * v2 compat only, for caching mutated $options + * @internal + */ + resolvedOptions?: MergedComponentOptions +} + +const emptyAppContext = createAppContext() + +let uid = 0 + +export function createComponentInstance( + vnode: VNode, + parent: ComponentInternalInstance | null, + suspense: SuspenseBoundary | null, +): ComponentInternalInstance { + const type = vnode.type as ConcreteComponent + // inherit parent app context - or - if root, adopt from root vnode + const appContext = + (parent ? parent.appContext : vnode.appContext) || emptyAppContext + + const instance: ComponentInternalInstance = { + uid: uid++, + vnode, + type, + parent, + appContext, + root: null!, // to be immediately set + next: null, + subTree: null!, // will be set synchronously right after creation + effect: null!, + update: null!, // will be set synchronously right after creation + scope: new EffectScope(true /* detached */), + render: null, + proxy: null, + exposed: null, + exposeProxy: null, + withProxy: null, + + provides: parent ? parent.provides : Object.create(appContext.provides), + accessCache: null!, + renderCache: [], + + // local resolved assets + components: null, + directives: null, + + // resolved props and emits options + propsOptions: normalizePropsOptions(type, appContext), + emitsOptions: normalizeEmitsOptions(type, appContext), + + // emit + emit: null!, // to be set immediately + emitted: null, + + // props default value + propsDefaults: EMPTY_OBJ, + + // inheritAttrs + inheritAttrs: type.inheritAttrs, + + // state + ctx: EMPTY_OBJ, + data: EMPTY_OBJ, + props: EMPTY_OBJ, + attrs: EMPTY_OBJ, + slots: EMPTY_OBJ, + refs: EMPTY_OBJ, + setupState: EMPTY_OBJ, + setupContext: null, + + attrsProxy: null, + slotsProxy: null, + + // suspense related + suspense, + suspenseId: suspense ? suspense.pendingId : 0, + asyncDep: null, + asyncResolved: false, + + // lifecycle hooks + // not using enums here because it results in computed properties + isMounted: false, + isUnmounted: false, + isDeactivated: false, + bc: null, + c: null, + bm: null, + m: null, + bu: null, + u: null, + um: null, + bum: null, + da: null, + a: null, + rtg: null, + rtc: null, + ec: null, + sp: null, + } + if (__DEV__) { + instance.ctx = createDevRenderContext(instance) + } else { + instance.ctx = { _: instance } + } + instance.root = parent ? parent.root : instance + instance.emit = emit.bind(null, instance) + + // apply custom element special handling + if (vnode.ce) { + vnode.ce(instance) + } + + return instance +} + +export let currentInstance: ComponentInternalInstance | null = null + +export const getCurrentInstance: () => ComponentInternalInstance | null = () => + currentInstance || currentRenderingInstance + +let internalSetCurrentInstance: ( + instance: ComponentInternalInstance | null, +) => void +let setInSSRSetupState: (state: boolean) => void + +/** + * The following makes getCurrentInstance() usage across multiple copies of Vue + * work. Some cases of how this can happen are summarized in #7590. In principle + * the duplication should be avoided, but in practice there are often cases + * where the user is unable to resolve on their own, especially in complicated + * SSR setups. + * + * Note this fix is technically incomplete, as we still rely on other singletons + * for effectScope and global reactive dependency maps. However, it does make + * some of the most common cases work. It also warns if the duplication is + * found during browser execution. + */ +if (__SSR__) { + type Setter = (v: any) => void + const g = getGlobalThis() + const registerGlobalSetter = (key: string, setter: Setter) => { + let setters: Setter[] + if (!(setters = g[key])) setters = g[key] = [] + setters.push(setter) + return (v: any) => { + if (setters.length > 1) setters.forEach(set => set(v)) + else setters[0](v) + } + } + internalSetCurrentInstance = registerGlobalSetter( + `__VUE_INSTANCE_SETTERS__`, + v => (currentInstance = v), + ) + // also make `isInSSRComponentSetup` sharable across copies of Vue. + // this is needed in the SFC playground when SSRing async components, since + // we have to load both the runtime and the server-renderer from CDNs, they + // contain duplicated copies of Vue runtime code. + setInSSRSetupState = registerGlobalSetter( + `__VUE_SSR_SETTERS__`, + v => (isInSSRComponentSetup = v), + ) +} else { + internalSetCurrentInstance = i => { + currentInstance = i + } + setInSSRSetupState = v => { + isInSSRComponentSetup = v + } +} + +export const setCurrentInstance = (instance: ComponentInternalInstance) => { + const prev = currentInstance + internalSetCurrentInstance(instance) + instance.scope.on() + return (): void => { + instance.scope.off() + internalSetCurrentInstance(prev) + } +} + +export const unsetCurrentInstance = (): void => { + currentInstance && currentInstance.scope.off() + internalSetCurrentInstance(null) +} + +const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component') + +export function validateComponentName( + name: string, + { isNativeTag }: AppConfig, +): void { + if (isBuiltInTag(name) || isNativeTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component id: ' + name, + ) + } +} + +export function isStatefulComponent(instance: ComponentInternalInstance): number { + return instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT +} + +export let isInSSRComponentSetup = false + +export function setupComponent( + instance: ComponentInternalInstance, + isSSR = false, +): Promise | undefined { + isSSR && setInSSRSetupState(isSSR) + + const { props, children } = instance.vnode + const isStateful = isStatefulComponent(instance) + initProps(instance, props, isStateful, isSSR) + initSlots(instance, children) + + const setupResult = isStateful + ? setupStatefulComponent(instance, isSSR) + : undefined + + isSSR && setInSSRSetupState(false) + return setupResult +} + +function setupStatefulComponent( + instance: ComponentInternalInstance, + isSSR: boolean, +) { + const Component = instance.type as ComponentOptions + + if (__DEV__) { + if (Component.name) { + validateComponentName(Component.name, instance.appContext.config) + } + if (Component.components) { + const names = Object.keys(Component.components) + for (let i = 0; i < names.length; i++) { + validateComponentName(names[i], instance.appContext.config) + } + } + if (Component.directives) { + const names = Object.keys(Component.directives) + for (let i = 0; i < names.length; i++) { + validateDirectiveName(names[i]) + } + } + if (Component.compilerOptions && isRuntimeOnly()) { + warn( + `"compilerOptions" is only supported when using a build of Vue that ` + + `includes the runtime compiler. Since you are using a runtime-only ` + + `build, the options should be passed via your build tool config instead.`, + ) + } + } + // 0. create render proxy property access cache + instance.accessCache = Object.create(null) + // 1. create public instance / render proxy + instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) + if (__DEV__) { + exposePropsOnRenderContext(instance) + } + // 2. call setup() + const { setup } = Component + if (setup) { + const setupContext = (instance.setupContext = + setup.length > 1 ? createSetupContext(instance) : null) + + const reset = setCurrentInstance(instance) + pauseTracking() + const setupResult = callWithErrorHandling( + setup, + instance, + ErrorCodes.SETUP_FUNCTION, + [ + __DEV__ ? shallowReadonly(instance.props) : instance.props, + setupContext, + ], + ) + resetTracking() + reset() + + if (isPromise(setupResult)) { + setupResult.then(unsetCurrentInstance, unsetCurrentInstance) + if (isSSR) { + // return the promise so server-renderer can wait on it + return setupResult + .then((resolvedResult: unknown) => { + handleSetupResult(instance, resolvedResult, isSSR) + }) + .catch(e => { + handleError(e, instance, ErrorCodes.SETUP_FUNCTION) + }) + } else if (__FEATURE_SUSPENSE__) { + // async setup returned Promise. + // bail here and wait for re-entry. + instance.asyncDep = setupResult + if (__DEV__ && !instance.suspense) { + const name = Component.name ?? 'Anonymous' + warn( + `Component <${name}>: setup function returned a promise, but no ` + + ` boundary was found in the parent component tree. ` + + `A component with async setup() must be nested in a ` + + `in order to be rendered.`, + ) + } + } else if (__DEV__) { + warn( + `setup() returned a Promise, but the version of Vue you are using ` + + `does not support it yet.`, + ) + } + } else { + handleSetupResult(instance, setupResult, isSSR) + } + } else { + finishComponentSetup(instance, isSSR) + } +} + +export function handleSetupResult( + instance: ComponentInternalInstance, + setupResult: unknown, + isSSR: boolean, +): void { + if (isFunction(setupResult)) { + // setup returned an inline render function + if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) { + // when the function's name is `ssrRender` (compiled by SFC inline mode), + // set it as ssrRender instead. + instance.ssrRender = setupResult + } else { + instance.render = setupResult as InternalRenderFunction + } + } else if (isObject(setupResult)) { + if (__DEV__ && isVNode(setupResult)) { + warn( + `setup() should not return VNodes directly - ` + + `return a render function instead.`, + ) + } + // setup returned bindings. + // assuming a render function compiled from template is present. + if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { + instance.devtoolsRawSetupState = setupResult + } + instance.setupState = proxyRefs(setupResult) + if (__DEV__) { + exposeSetupStateOnRenderContext(instance) + } + } else if (__DEV__ && setupResult !== undefined) { + warn( + `setup() should return an object. Received: ${ + setupResult === null ? 'null' : typeof setupResult + }`, + ) + } + finishComponentSetup(instance, isSSR) +} + +type CompileFunction = ( + template: string | object, + options?: CompilerOptions, +) => InternalRenderFunction + +let compile: CompileFunction | undefined +let installWithProxy: (i: ComponentInternalInstance) => void + +/** + * For runtime-dom to register the compiler. + * Note the exported method uses any to avoid d.ts relying on the compiler types. + */ +export function registerRuntimeCompiler(_compile: any): void { + compile = _compile + installWithProxy = i => { + if (i.render!._rc) { + i.withProxy = new Proxy(i.ctx, RuntimeCompiledPublicInstanceProxyHandlers) + } + } +} + +// dev only +export const isRuntimeOnly = (): boolean => !compile + +export function finishComponentSetup( + instance: ComponentInternalInstance, + isSSR: boolean, + skipOptions?: boolean, +): void { + const Component = instance.type as ComponentOptions + + if (__COMPAT__) { + convertLegacyRenderFn(instance) + + if (__DEV__ && Component.compatConfig) { + validateCompatConfig(Component.compatConfig) + } + } + + // template / render function normalization + // could be already set when returned from setup() + if (!instance.render) { + // only do on-the-fly compile if not in SSR - SSR on-the-fly compilation + // is done by server-renderer + if (!isSSR && compile && !Component.render) { + const template = + (__COMPAT__ && + instance.vnode.props && + instance.vnode.props['inline-template']) || + Component.template || + resolveMergedOptions(instance).template + if (template) { + if (__DEV__) { + startMeasure(instance, `compile`) + } + const { isCustomElement, compilerOptions } = instance.appContext.config + const { delimiters, compilerOptions: componentCompilerOptions } = + Component + const finalCompilerOptions: CompilerOptions = extend( + extend( + { + isCustomElement, + delimiters, + }, + compilerOptions, + ), + componentCompilerOptions, + ) + if (__COMPAT__) { + // pass runtime compat config into the compiler + finalCompilerOptions.compatConfig = Object.create(globalCompatConfig) + if (Component.compatConfig) { + // @ts-expect-error types are not compatible + extend(finalCompilerOptions.compatConfig, Component.compatConfig) + } + } + Component.render = compile(template, finalCompilerOptions) + if (__DEV__) { + endMeasure(instance, `compile`) + } + } + } + + instance.render = (Component.render || NOOP) as InternalRenderFunction + + // for runtime-compiled render functions using `with` blocks, the render + // proxy used needs a different `has` handler which is more performant and + // also only allows a whitelist of globals to fallthrough. + if (installWithProxy) { + installWithProxy(instance) + } + } + + // support for 2.x options + if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) { + const reset = setCurrentInstance(instance) + pauseTracking() + try { + applyOptions(instance) + } finally { + resetTracking() + reset() + } + } + + // warn missing template/render + // the runtime compilation of template in SSR is done by server-render + if (__DEV__ && !Component.render && instance.render === NOOP && !isSSR) { + /* istanbul ignore if */ + if (!compile && Component.template) { + warn( + `Component provided template option but ` + + `runtime compilation is not supported in this build of Vue.` + + (__ESM_BUNDLER__ + ? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".` + : __ESM_BROWSER__ + ? ` Use "vue.esm-browser.js" instead.` + : __GLOBAL__ + ? ` Use "vue.global.js" instead.` + : ``) /* should not happen */, + ) + } else { + warn(`Component is missing template or render function: `, Component) + } + } +} + +const attrsProxyHandlers = __DEV__ + ? { + get(target: Data, key: string) { + markAttrsAccessed() + track(target, TrackOpTypes.GET, '') + return target[key] + }, + set() { + warn(`setupContext.attrs is readonly.`) + return false + }, + deleteProperty() { + warn(`setupContext.attrs is readonly.`) + return false + }, + } + : { + get(target: Data, key: string) { + track(target, TrackOpTypes.GET, '') + return target[key] + }, + } + +/** + * Dev-only + */ +function getSlotsProxy(instance: ComponentInternalInstance): Slots { + return ( + instance.slotsProxy || + (instance.slotsProxy = new Proxy(instance.slots, { + get(target, key: string) { + track(instance, TrackOpTypes.GET, '$slots') + return target[key] + }, + })) + ) +} + +export function createSetupContext( + instance: ComponentInternalInstance, +): SetupContext { + const expose: SetupContext['expose'] = exposed => { + if (__DEV__) { + if (instance.exposed) { + warn(`expose() should be called only once per setup().`) + } + if (exposed != null) { + let exposedType: string = typeof exposed + if (exposedType === 'object') { + if (isArray(exposed)) { + exposedType = 'array' + } else if (isRef(exposed)) { + exposedType = 'ref' + } + } + if (exposedType !== 'object') { + warn( + `expose() should be passed a plain object, received ${exposedType}.`, + ) + } + } + } + instance.exposed = exposed || {} + } + + if (__DEV__) { + // We use getters in dev in case libs like test-utils overwrite instance + // properties (overwrites should not be done in prod) + let attrsProxy: Data + return Object.freeze({ + get attrs() { + return ( + attrsProxy || + (attrsProxy = new Proxy(instance.attrs, attrsProxyHandlers)) + ) + }, + get slots() { + return getSlotsProxy(instance) + }, + get emit() { + return (event: string, ...args: any[]) => instance.emit(event, ...args) + }, + expose, + }) + } else { + return { + attrs: new Proxy(instance.attrs, attrsProxyHandlers), + slots: instance.slots, + emit: instance.emit, + expose, + } + } +} + +export function getComponentPublicInstance( + instance: ComponentInternalInstance, +): Record | ComponentPublicInstance | null { + if (instance.exposed) { + return ( + instance.exposeProxy || + (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), { + get(target, key: string) { + if (key in target) { + return target[key] + } else if (key in publicPropertiesMap) { + return publicPropertiesMap[key](instance) + } + }, + has(target, key: string) { + return key in target || key in publicPropertiesMap + }, + })) + ) + } else { + return instance.proxy + } +} + +const classifyRE = /(?:^|[-_])(\w)/g +const classify = (str: string): string => + str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '') + +export function getComponentName( + Component: ConcreteComponent, + includeInferred = true, +): string | false | undefined { + return isFunction(Component) + ? Component.displayName || Component.name + : Component.name || (includeInferred && Component.__name) +} + +/* istanbul ignore next */ +export function formatComponentName( + instance: ComponentInternalInstance | null, + Component: ConcreteComponent, + isRoot = false, +): string { + let name = getComponentName(Component) + if (!name && Component.__file) { + const match = Component.__file.match(/([^/\\]+)\.\w+$/) + if (match) { + name = match[1] + } + } + + if (!name && instance && instance.parent) { + // try to infer the name based on reverse resolution + const inferFromRegistry = (registry: Record | undefined) => { + for (const key in registry) { + if (registry[key] === Component) { + return key + } + } + } + name = + inferFromRegistry( + instance.components || + (instance.parent.type as ComponentOptions).components, + ) || inferFromRegistry(instance.appContext.components) + } + + return name ? classify(name) : isRoot ? `App` : `Anonymous` +} + +export function isClassComponent(value: unknown): value is ClassComponent { + return isFunction(value) && '__vccOpts' in value +} + +import { + Comment, + Fragment, + Static, + Text, + type VNode, + type VNodeArrayChildren, + type VNodeHook, + type VNodeProps, + cloneIfMounted, + createVNode, + invokeVNodeHook, + isSameVNodeType, + normalizeVNode, +} from './vnode' +import { + type ComponentInternalInstance, + type ComponentOptions, + type Data, + type LifecycleHook, + createComponentInstance, + setupComponent, +} from './component' +import { + filterSingleRoot, + renderComponentRoot, + shouldUpdateComponent, + updateHOCHostEl, +} from './componentRenderUtils' +import { + EMPTY_ARR, + EMPTY_OBJ, + NOOP, + PatchFlags, + ShapeFlags, + getGlobalThis, + invokeArrayFns, + isArray, + isReservedProp, +} from '@vue/shared' +import { + type SchedulerJob, + flushPostFlushCbs, + flushPreFlushCbs, + invalidateJob, + queueJob, + queuePostFlushCb, +} from './scheduler' +import { ReactiveEffect, pauseTracking, resetTracking } from '@vue/reactivity' +import { updateProps } from './componentProps' +import { updateSlots } from './componentSlots' +import { popWarningContext, pushWarningContext, warn } from './warning' +import { type CreateAppFunction, createAppAPI } from './apiCreateApp' +import { setRef } from './rendererTemplateRef' +import { + type SuspenseBoundary, + type SuspenseImpl, + queueEffectWithSuspense, +} from './components/Suspense' +import type { TeleportImpl, TeleportVNode } from './components/Teleport' +import { type KeepAliveContext, isKeepAlive } from './components/KeepAlive' +import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr' +import { type RootHydrateFunction, createHydrationFunctions } from './hydration' +import { invokeDirectiveHook } from './directives' +import { endMeasure, startMeasure } from './profiling' +import { + devtoolsComponentAdded, + devtoolsComponentRemoved, + devtoolsComponentUpdated, + setDevtoolsHook, +} from './devtools' +import { initFeatureFlags } from './featureFlags' +import { isAsyncWrapper } from './apiAsyncComponent' +import { isCompatEnabled } from './compat/compatConfig' +import { DeprecationTypes } from './compat/compatConfig' +import type { TransitionHooks } from './components/BaseTransition' + +export interface Renderer { + render: RootRenderFunction + createApp: CreateAppFunction +} + +export interface HydrationRenderer extends Renderer { + hydrate: RootHydrateFunction +} + +export type ElementNamespace = 'svg' | 'mathml' | undefined + +export type RootRenderFunction = ( + vnode: VNode | null, + container: HostElement, + namespace?: ElementNamespace, +) => void + +export interface RendererOptions< + HostNode = RendererNode, + HostElement = RendererElement, +> { + patchProp( + el: HostElement, + key: string, + prevValue: any, + nextValue: any, + namespace?: ElementNamespace, + prevChildren?: VNode[], + parentComponent?: ComponentInternalInstance | null, + parentSuspense?: SuspenseBoundary | null, + unmountChildren?: UnmountChildrenFn, + ): void + insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void + remove(el: HostNode): void + createElement( + type: string, + namespace?: ElementNamespace, + isCustomizedBuiltIn?: string, + vnodeProps?: (VNodeProps & { [key: string]: any }) | null, + ): HostElement + createText(text: string): HostNode + createComment(text: string): HostNode + setText(node: HostNode, text: string): void + setElementText(node: HostElement, text: string): void + parentNode(node: HostNode): HostElement | null + nextSibling(node: HostNode): HostNode | null + querySelector?(selector: string): HostElement | null + setScopeId?(el: HostElement, id: string): void + cloneNode?(node: HostNode): HostNode + insertStaticContent?( + content: string, + parent: HostElement, + anchor: HostNode | null, + namespace: ElementNamespace, + start?: HostNode | null, + end?: HostNode | null, + ): [HostNode, HostNode] +} + +// Renderer Node can technically be any object in the context of core renderer +// logic - they are never directly operated on and always passed to the node op +// functions provided via options, so the internal constraint is really just +// a generic object. +export interface RendererNode { + [key: string]: any +} + +export interface RendererElement extends RendererNode {} + +// An object exposing the internals of a renderer, passed to tree-shakeable +// features so that they can be decoupled from this file. Keys are shortened +// to optimize bundle size. +export interface RendererInternals< + HostNode = RendererNode, + HostElement = RendererElement, +> { + p: PatchFn + um: UnmountFn + r: RemoveFn + m: MoveFn + mt: MountComponentFn + mc: MountChildrenFn + pc: PatchChildrenFn + pbc: PatchBlockChildrenFn + n: NextFn + o: RendererOptions +} + +// These functions are created inside a closure and therefore their types cannot +// be directly exported. In order to avoid maintaining function signatures in +// two places, we declare them once here and use them inside the closure. +type PatchFn = ( + n1: VNode | null, // null means this is a mount + n2: VNode, + container: RendererElement, + anchor?: RendererNode | null, + parentComponent?: ComponentInternalInstance | null, + parentSuspense?: SuspenseBoundary | null, + namespace?: ElementNamespace, + slotScopeIds?: string[] | null, + optimized?: boolean, +) => void + +type MountChildrenFn = ( + children: VNodeArrayChildren, + container: RendererElement, + anchor: RendererNode | null, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + namespace: ElementNamespace, + slotScopeIds: string[] | null, + optimized: boolean, + start?: number, +) => void + +type PatchChildrenFn = ( + n1: VNode | null, + n2: VNode, + container: RendererElement, + anchor: RendererNode | null, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + namespace: ElementNamespace, + slotScopeIds: string[] | null, + optimized: boolean, +) => void + +type PatchBlockChildrenFn = ( + oldChildren: VNode[], + newChildren: VNode[], + fallbackContainer: RendererElement, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + namespace: ElementNamespace, + slotScopeIds: string[] | null, +) => void + +type MoveFn = ( + vnode: VNode, + container: RendererElement, + anchor: RendererNode | null, + type: MoveType, + parentSuspense?: SuspenseBoundary | null, +) => void + +type NextFn = (vnode: VNode) => RendererNode | null + +type UnmountFn = ( + vnode: VNode, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + doRemove?: boolean, + optimized?: boolean, +) => void + +type RemoveFn = (vnode: VNode) => void + +type UnmountChildrenFn = ( + children: VNode[], + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + doRemove?: boolean, + optimized?: boolean, + start?: number, +) => void + +export type MountComponentFn = ( + initialVNode: VNode, + container: RendererElement, + anchor: RendererNode | null, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + namespace: ElementNamespace, + optimized: boolean, +) => void + +type ProcessTextOrCommentFn = ( + n1: VNode | null, + n2: VNode, + container: RendererElement, + anchor: RendererNode | null, +) => void + +export type SetupRenderEffectFn = ( + instance: ComponentInternalInstance, + initialVNode: VNode, + container: RendererElement, + anchor: RendererNode | null, + parentSuspense: SuspenseBoundary | null, + namespace: ElementNamespace, + optimized: boolean, +) => void + +export enum MoveType { + ENTER, + LEAVE, + REORDER, +} + +export const queuePostRenderEffect: typeof queuePostFlushCb | ((fn: Function | Function[], suspense: SuspenseBoundary | null) => void) = __FEATURE_SUSPENSE__ + ? __TEST__ + ? // vitest can't seem to handle eager circular dependency + (fn: Function | Function[], suspense: SuspenseBoundary | null) => + queueEffectWithSuspense(fn, suspense) + : queueEffectWithSuspense + : queuePostFlushCb + +/** + * The createRenderer function accepts two generic arguments: + * HostNode and HostElement, corresponding to Node and Element types in the + * host environment. For example, for runtime-dom, HostNode would be the DOM + * `Node` interface and HostElement would be the DOM `Element` interface. + * + * Custom renderers can pass in the platform specific types like this: + * + * ``` js + * const { render, createApp } = createRenderer({ + * patchProp, + * ...nodeOps + * }) + * ``` + */ +export function createRenderer< + HostNode = RendererNode, + HostElement = RendererElement, +>(options: RendererOptions): Renderer { + return baseCreateRenderer(options) +} + +// Separate API for creating hydration-enabled renderer. +// Hydration logic is only used when calling this function, making it +// tree-shakable. +export function createHydrationRenderer( + options: RendererOptions, +): HydrationRenderer { + return baseCreateRenderer(options, createHydrationFunctions) +} + +// overload 1: no hydration +function baseCreateRenderer< + HostNode = RendererNode, + HostElement = RendererElement, +>(options: RendererOptions): Renderer + +// overload 2: with hydration +function baseCreateRenderer( + options: RendererOptions, + createHydrationFns: typeof createHydrationFunctions, +): HydrationRenderer + +// implementation +function baseCreateRenderer( + options: RendererOptions, + createHydrationFns?: typeof createHydrationFunctions, +): any { + // compile-time feature flags check + if (__ESM_BUNDLER__ && !__TEST__) { + initFeatureFlags() + } + + const target = getGlobalThis() + target.__VUE__ = true + if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { + setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target) + } + + const { + insert: hostInsert, + remove: hostRemove, + patchProp: hostPatchProp, + createElement: hostCreateElement, + createText: hostCreateText, + createComment: hostCreateComment, + setText: hostSetText, + setElementText: hostSetElementText, + parentNode: hostParentNode, + nextSibling: hostNextSibling, + setScopeId: hostSetScopeId = NOOP, + insertStaticContent: hostInsertStaticContent, + } = options + + // Note: functions inside this closure should use `const xxx = () => {}` + // style in order to prevent being inlined by minifiers. + const patch: PatchFn = ( + n1, + n2, + container, + anchor = null, + parentComponent = null, + parentSuspense = null, + namespace = undefined, + slotScopeIds = null, + optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren, + ) => { + if (n1 === n2) { + return + } + + // patching & not same type, unmount old tree + if (n1 && !isSameVNodeType(n1, n2)) { + anchor = getNextHostNode(n1) + unmount(n1, parentComponent, parentSuspense, true) + n1 = null + } + + if (n2.patchFlag === PatchFlags.BAIL) { + optimized = false + n2.dynamicChildren = null + } + + const { type, ref, shapeFlag } = n2 + switch (type) { + case Text: + processText(n1, n2, container, anchor) + break + case Comment: + processCommentNode(n1, n2, container, anchor) + break + case Static: + if (n1 == null) { + mountStaticNode(n2, container, anchor, namespace) + } else if (__DEV__) { + patchStaticNode(n1, n2, container, namespace) + } + break + case Fragment: + processFragment( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + ) + break + default: + if (shapeFlag & ShapeFlags.ELEMENT) { + processElement( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + ) + } else if (shapeFlag & ShapeFlags.COMPONENT) { + processComponent( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + ) + } else if (shapeFlag & ShapeFlags.TELEPORT) { + ;(type as typeof TeleportImpl).process( + n1 as TeleportVNode, + n2 as TeleportVNode, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + internals, + ) + } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { + ;(type as typeof SuspenseImpl).process( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + internals, + ) + } else if (__DEV__) { + warn('Invalid VNode type:', type, `(${typeof type})`) + } + } + + // set ref + if (ref != null && parentComponent) { + setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2) + } + } + + const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) => { + if (n1 == null) { + hostInsert( + (n2.el = hostCreateText(n2.children as string)), + container, + anchor, + ) + } else { + const el = (n2.el = n1.el!) + if (n2.children !== n1.children) { + hostSetText(el, n2.children as string) + } + } + } + + const processCommentNode: ProcessTextOrCommentFn = ( + n1, + n2, + container, + anchor, + ) => { + if (n1 == null) { + hostInsert( + (n2.el = hostCreateComment((n2.children as string) || '')), + container, + anchor, + ) + } else { + // there's no support for dynamic comments + n2.el = n1.el + } + } + + const mountStaticNode = ( + n2: VNode, + container: RendererElement, + anchor: RendererNode | null, + namespace: ElementNamespace, + ) => { + // static nodes are only present when used with compiler-dom/runtime-dom + // which guarantees presence of hostInsertStaticContent. + ;[n2.el, n2.anchor] = hostInsertStaticContent!( + n2.children as string, + container, + anchor, + namespace, + n2.el, + n2.anchor, + ) + } + + /** + * Dev / HMR only + */ + const patchStaticNode = ( + n1: VNode, + n2: VNode, + container: RendererElement, + namespace: ElementNamespace, + ) => { + // static nodes are only patched during dev for HMR + if (n2.children !== n1.children) { + const anchor = hostNextSibling(n1.anchor!) + // remove existing + removeStaticNode(n1) + // insert new + ;[n2.el, n2.anchor] = hostInsertStaticContent!( + n2.children as string, + container, + anchor, + namespace, + ) + } else { + n2.el = n1.el + n2.anchor = n1.anchor + } + } + + const moveStaticNode = ( + { el, anchor }: VNode, + container: RendererElement, + nextSibling: RendererNode | null, + ) => { + let next + while (el && el !== anchor) { + next = hostNextSibling(el) + hostInsert(el, container, nextSibling) + el = next + } + hostInsert(anchor!, container, nextSibling) + } + + const removeStaticNode = ({ el, anchor }: VNode) => { + let next + while (el && el !== anchor) { + next = hostNextSibling(el) + hostRemove(el) + el = next + } + hostRemove(anchor!) + } + + const processElement = ( + n1: VNode | null, + n2: VNode, + container: RendererElement, + anchor: RendererNode | null, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + namespace: ElementNamespace, + slotScopeIds: string[] | null, + optimized: boolean, + ) => { + if (n2.type === 'svg') { + namespace = 'svg' + } else if (n2.type === 'math') { + namespace = 'mathml' + } + + if (n1 == null) { + mountElement( + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + ) + } else { + patchElement( + n1, + n2, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + ) + } + } + + const mountElement = ( + vnode: VNode, + container: RendererElement, + anchor: RendererNode | null, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + namespace: ElementNamespace, + slotScopeIds: string[] | null, + optimized: boolean, + ) => { + let el: RendererElement + let vnodeHook: VNodeHook | undefined | null + const { props, shapeFlag, transition, dirs } = vnode + + el = vnode.el = hostCreateElement( + vnode.type as string, + namespace, + props && props.is, + props, + ) + + // mount children first, since some props may rely on child content + // being already rendered, e.g. `