mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
feat: TC39 decorator proposal support (#22040)
This commit adds support for [TC39 Decorator Proposal](https://github.com/tc39/proposal-decorators). These decorators are only available in transpiled sources - ie. non-JavaScript files (because of lack of support in V8). This entails that "experimental TypeScript decorators" are not available by default and require to be configured, with a configuration like this: ``` { "compilerOptions": { "experimentalDecorators": true } } ``` Closes https://github.com/denoland/deno/issues/19160 --------- Signed-off-by: Bartek Iwańczuk <biwanczuk@gmail.com> Co-authored-by: crowlkats <crowlkats@toaxl.com> Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
This commit is contained in:
parent
aac0ad32bd
commit
b66f5ed00e
16 changed files with 145 additions and 32 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1172,9 +1172,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_config"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fec482ce578388108cfd4a7ca900be7b8aea5081e1922ff3c5f87f0767c531"
|
||||
checksum = "cb5634c4d8f29f62bc4516a3dc474c1a06a6ce1388a139df08cb2020244e7de7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"glob",
|
||||
|
|
|
@ -55,7 +55,7 @@ winres.workspace = true
|
|||
[dependencies]
|
||||
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
|
||||
deno_cache_dir = "=0.6.1"
|
||||
deno_config = "=0.8.1"
|
||||
deno_config = "=0.8.2"
|
||||
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
||||
deno_doc = { version = "=0.94.1", features = ["html"] }
|
||||
deno_emit = "=0.34.0"
|
||||
|
|
|
@ -158,10 +158,8 @@ pub fn ts_config_to_emit_options(
|
|||
_ => (false, false, false, false),
|
||||
};
|
||||
deno_ast::EmitOptions {
|
||||
// TODO(bartlomieju): change it to default to `false` and only enable
|
||||
// if tsconfig.json enabled experimental decorators
|
||||
use_ts_decorators: true,
|
||||
use_decorators_proposal: false,
|
||||
use_ts_decorators: options.experimental_decorators,
|
||||
use_decorators_proposal: !options.experimental_decorators,
|
||||
emit_metadata: options.emit_decorator_metadata,
|
||||
imports_not_used_as_values,
|
||||
inline_source_map: options.inline_source_map,
|
||||
|
@ -1088,10 +1086,26 @@ impl CliOptions {
|
|||
&self,
|
||||
config_type: TsConfigType,
|
||||
) -> Result<TsConfigForEmit, AnyError> {
|
||||
deno_config::get_ts_config_for_emit(
|
||||
let result = deno_config::get_ts_config_for_emit(
|
||||
config_type,
|
||||
self.maybe_config_file.as_ref(),
|
||||
)
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(mut ts_config_for_emit) => {
|
||||
if matches!(self.flags.subcommand, DenoSubcommand::Bundle(..)) {
|
||||
// For backwards compatibility, force `experimentalDecorators` setting
|
||||
// to true.
|
||||
*ts_config_for_emit
|
||||
.ts_config
|
||||
.0
|
||||
.get_mut("experimentalDecorators")
|
||||
.unwrap() = serde_json::Value::Bool(true);
|
||||
}
|
||||
Ok(ts_config_for_emit)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_inspector_server(&self) -> Option<InspectorServer> {
|
||||
|
|
|
@ -358,15 +358,26 @@ fn typescript_declarations() {
|
|||
|
||||
#[test]
|
||||
fn typescript_decorators() {
|
||||
util::with_pty(&["repl"], |mut console| {
|
||||
console
|
||||
.write_line("function dec(target) { target.prototype.test = () => 2; }");
|
||||
console.expect("undefined");
|
||||
console.write_line("@dec class Test {}");
|
||||
console.expect("[class Test]");
|
||||
console.write_line("new Test().test()");
|
||||
console.expect("2");
|
||||
});
|
||||
let context = TestContextBuilder::default().use_temp_cwd().build();
|
||||
let temp_dir = context.temp_dir();
|
||||
temp_dir.write(
|
||||
"./deno.json",
|
||||
r#"{ "compilerOptions": { "experimentalDecorators": true } }"#,
|
||||
);
|
||||
let config_path = temp_dir.target_path().join("./deno.json");
|
||||
util::with_pty(
|
||||
&["repl", "--config", config_path.to_string_lossy().as_ref()],
|
||||
|mut console| {
|
||||
console.write_line(
|
||||
"function dec(target) { target.prototype.test = () => 2; }",
|
||||
);
|
||||
console.expect("undefined");
|
||||
console.write_line("@dec class Test {}");
|
||||
console.expect("[class Test]");
|
||||
console.write_line("new Test().test()");
|
||||
console.expect("2");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1508,8 +1508,13 @@ itest!(no_check {
|
|||
});
|
||||
|
||||
itest!(no_check_decorators {
|
||||
args: "run --quiet --reload --no-check run/no_check_decorators.ts",
|
||||
output: "run/no_check_decorators.ts.out",
|
||||
args: "run --quiet --reload --no-check run/decorators/experimental/no_check/main.ts",
|
||||
output: "run/decorators/experimental/no_check/main.out",
|
||||
});
|
||||
|
||||
itest!(decorators_tc39_proposal {
|
||||
args: "run --quiet --reload --check run/decorators/tc39_proposal/main.ts",
|
||||
output: "run/decorators/tc39_proposal/main.out",
|
||||
});
|
||||
|
||||
itest!(check_remote {
|
||||
|
@ -1526,8 +1531,8 @@ itest!(no_check_remote {
|
|||
});
|
||||
|
||||
itest!(runtime_decorators {
|
||||
args: "run --quiet --reload --no-check run/runtime_decorators.ts",
|
||||
output: "run/runtime_decorators.ts.out",
|
||||
args: "run --quiet --reload --no-check run/decorators/experimental/runtime/main.ts",
|
||||
output: "run/decorators/experimental/runtime/main.out",
|
||||
});
|
||||
|
||||
itest!(seed_random {
|
||||
|
@ -1591,8 +1596,8 @@ itest!(ts_type_imports {
|
|||
});
|
||||
|
||||
itest!(ts_decorators {
|
||||
args: "run --reload --check run/ts_decorators.ts",
|
||||
output: "run/ts_decorators.ts.out",
|
||||
args: "run --reload --check run/decorators/experimental/ts/main.ts",
|
||||
output: "run/decorators/experimental/ts/main.out",
|
||||
});
|
||||
|
||||
itest!(ts_type_only_import {
|
||||
|
|
5
cli/tests/testdata/run/decorators/experimental/deno.json
vendored
Normal file
5
cli/tests/testdata/run/decorators/experimental/deno.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
7
cli/tests/testdata/run/decorators/experimental/runtime/main.out
vendored
Normal file
7
cli/tests/testdata/run/decorators/experimental/runtime/main.out
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
@A evaluated
|
||||
@B evaluated
|
||||
@B called
|
||||
@A called
|
||||
fn() called from @A
|
||||
fn() called from @B
|
||||
C.test() called
|
42
cli/tests/testdata/run/decorators/experimental/runtime/main.ts
vendored
Normal file
42
cli/tests/testdata/run/decorators/experimental/runtime/main.ts
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
// deno-lint-ignore-file
|
||||
function a() {
|
||||
console.log("@A evaluated");
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
console.log("@A called");
|
||||
const fn = descriptor.value;
|
||||
descriptor.value = function () {
|
||||
console.log("fn() called from @A");
|
||||
fn();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function b() {
|
||||
console.log("@B evaluated");
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
console.log("@B called");
|
||||
const fn = descriptor.value;
|
||||
descriptor.value = function () {
|
||||
console.log("fn() called from @B");
|
||||
fn();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class C {
|
||||
@a()
|
||||
@b()
|
||||
static test() {
|
||||
console.log("C.test() called");
|
||||
}
|
||||
}
|
||||
|
||||
C.test();
|
3
cli/tests/testdata/run/decorators/tc39_proposal/main.out
vendored
Normal file
3
cli/tests/testdata/run/decorators/tc39_proposal/main.out
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
starting m with arguments 1
|
||||
C.m 1
|
||||
ending m
|
21
cli/tests/testdata/run/decorators/tc39_proposal/main.ts
vendored
Normal file
21
cli/tests/testdata/run/decorators/tc39_proposal/main.ts
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// deno-lint-ignore no-explicit-any
|
||||
function logged(value: any, { kind, name }: { kind: string; name: string }) {
|
||||
if (kind === "method") {
|
||||
return function (...args: unknown[]) {
|
||||
console.log(`starting ${name} with arguments ${args.join(", ")}`);
|
||||
// @ts-ignore this has implicit any type
|
||||
const ret = value.call(this, ...args);
|
||||
console.log(`ending ${name}`);
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class C {
|
||||
@logged
|
||||
m(arg: number) {
|
||||
console.log("C.m", arg);
|
||||
}
|
||||
}
|
||||
|
||||
new C().m(1);
|
8
cli/tests/testdata/run/runtime_decorators.ts
vendored
8
cli/tests/testdata/run/runtime_decorators.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
// deno-lint-ignore-file
|
||||
function A() {
|
||||
function a() {
|
||||
console.log("@A evaluated");
|
||||
return function (
|
||||
target: any,
|
||||
|
@ -15,7 +15,7 @@ function A() {
|
|||
};
|
||||
}
|
||||
|
||||
function B() {
|
||||
function b() {
|
||||
console.log("@B evaluated");
|
||||
return function (
|
||||
target: any,
|
||||
|
@ -32,8 +32,8 @@ function B() {
|
|||
}
|
||||
|
||||
class C {
|
||||
@A()
|
||||
@B()
|
||||
@a()
|
||||
@b()
|
||||
static test() {
|
||||
console.log("C.test() called");
|
||||
}
|
||||
|
|
|
@ -184,6 +184,7 @@ pub struct ReplSession {
|
|||
/// This is only optional because it's temporarily taken when evaluating.
|
||||
test_event_receiver: Option<tokio::sync::mpsc::UnboundedReceiver<TestEvent>>,
|
||||
jsx: ReplJsxState,
|
||||
experimental_decorators: bool,
|
||||
}
|
||||
|
||||
impl ReplSession {
|
||||
|
@ -240,6 +241,11 @@ impl ReplSession {
|
|||
deno_core::resolve_path("./$deno$repl.ts", cli_options.initial_cwd())
|
||||
.unwrap();
|
||||
|
||||
let ts_config_for_emit = cli_options
|
||||
.resolve_ts_config_for_emit(deno_config::TsConfigType::Emit)?;
|
||||
let emit_options =
|
||||
crate::args::ts_config_to_emit_options(ts_config_for_emit.ts_config);
|
||||
let experimental_decorators = emit_options.use_ts_decorators;
|
||||
let mut repl_session = ReplSession {
|
||||
npm_resolver,
|
||||
resolver,
|
||||
|
@ -260,6 +266,7 @@ impl ReplSession {
|
|||
frag_factory: "React.Fragment".to_string(),
|
||||
import_source: None,
|
||||
},
|
||||
experimental_decorators,
|
||||
};
|
||||
|
||||
// inject prelude
|
||||
|
@ -596,10 +603,8 @@ impl ReplSession {
|
|||
|
||||
let transpiled_src = parsed_source
|
||||
.transpile(&deno_ast::EmitOptions {
|
||||
// TODO(bartlomieju): change it to default to `false` and only enable
|
||||
// if tsconfig.json enabled experimental decorators
|
||||
use_ts_decorators: true,
|
||||
use_decorators_proposal: false,
|
||||
use_ts_decorators: self.experimental_decorators,
|
||||
use_decorators_proposal: !self.experimental_decorators,
|
||||
emit_metadata: false,
|
||||
source_map: false,
|
||||
inline_source_map: false,
|
||||
|
|
Loading…
Add table
Reference in a new issue