refactor: strongly typed TSC ops (#20466)

Removes usage of `serde_json::Value` in several ops used in TSC, in
favor of using strongly typed structs. This will unblock more 
changes in https://github.com/denoland/deno/pull/20462.
This commit is contained in:
Bartek Iwańczuk 2023-09-12 02:55:57 +02:00 committed by GitHub
parent 950e0e9cd6
commit 82c2864065
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 57 deletions

View file

@ -16,8 +16,7 @@ mod ts {
use deno_core::OpState;
use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
use serde::Deserialize;
use serde_json::json;
use serde_json::Value;
use serde::Serialize;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
@ -28,17 +27,31 @@ mod ts {
specifier: String,
}
#[op]
fn op_build_info(state: &mut OpState) -> Value {
let build_specifier = "asset:///bootstrap.ts";
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct BuildInfoResponse {
build_specifier: String,
libs: Vec<String>,
node_built_in_module_names: Vec<String>,
}
let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES.to_vec();
let build_libs = state.borrow::<Vec<&str>>();
json!({
"buildSpecifier": build_specifier,
"libs": build_libs,
"nodeBuiltInModuleNames": node_built_in_module_names,
})
#[op]
fn op_build_info<'s>(state: &mut OpState) -> BuildInfoResponse {
let build_specifier = "asset:///bootstrap.ts".to_string();
let build_libs = state
.borrow::<Vec<&str>>()
.iter()
.map(|s| s.to_string())
.collect();
let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES
.iter()
.map(|s| s.to_string())
.collect();
BuildInfoResponse {
build_specifier,
libs: build_libs,
node_built_in_module_names,
}
}
#[op]
@ -46,18 +59,35 @@ mod ts {
false
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct ScriptVersionArgs {
specifier: String,
}
#[op]
fn op_script_version(
_state: &mut OpState,
_args: Value,
_args: ScriptVersionArgs,
) -> Result<Option<String>, AnyError> {
Ok(Some("1".to_string()))
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct LoadResponse {
data: String,
version: String,
script_kind: i32,
}
#[op]
// using the same op that is used in `tsc.rs` for loading modules and reading
// files, but a slightly different implementation at build time.
fn op_load(state: &mut OpState, args: LoadArgs) -> Result<Value, AnyError> {
fn op_load(
state: &mut OpState,
args: LoadArgs,
) -> Result<LoadResponse, AnyError> {
let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
let path_dts = state.borrow::<PathBuf>();
let re_asset = lazy_regex::regex!(r"asset:/{3}lib\.(\S+)\.d\.ts");
@ -65,12 +95,12 @@ mod ts {
// we need a basic file to send to tsc to warm it up.
if args.specifier == build_specifier {
Ok(json!({
"data": r#"Deno.writeTextFile("hello.txt", "hello deno!");"#,
"version": "1",
Ok(LoadResponse {
data: r#"Deno.writeTextFile("hello.txt", "hello deno!");"#.to_string(),
version: "1".to_string(),
// this corresponds to `ts.ScriptKind.TypeScript`
"scriptKind": 3
}))
script_kind: 3,
})
// specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
// parse out just the name so we can lookup the asset.
} else if let Some(caps) = re_asset.captures(&args.specifier) {
@ -84,12 +114,12 @@ mod ts {
path_dts.join(format!("lib.{lib}.d.ts"))
};
let data = std::fs::read_to_string(path)?;
Ok(json!({
"data": data,
"version": "1",
Ok(LoadResponse {
data,
version: "1".to_string(),
// this corresponds to `ts.ScriptKind.TypeScript`
"scriptKind": 3
}))
script_kind: 3,
})
} else {
Err(custom_error(
"InvalidSpecifier",

View file

@ -3252,26 +3252,29 @@ fn op_is_node_file(state: &mut OpState, path: String) -> bool {
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct LoadResponse {
data: Arc<str>,
script_kind: i32,
version: Option<String>,
}
#[op]
fn op_load(
state: &mut OpState,
args: SpecifierArgs,
) -> Result<Value, AnyError> {
) -> Result<Option<LoadResponse>, AnyError> {
let state = state.borrow_mut::<State>();
let mark = state.performance.mark("op_load", Some(&args));
let specifier = state.normalize_specifier(args.specifier)?;
let asset_or_document = state.get_asset_or_document(&specifier);
state.performance.measure(mark);
Ok(match asset_or_document {
Some(doc) => {
json!({
"data": doc.text(),
"scriptKind": crate::tsc::as_ts_script_kind(doc.media_type()),
"version": state.script_version(&specifier),
})
}
None => Value::Null,
})
Ok(asset_or_document.map(|doc| LoadResponse {
data: doc.text(),
script_kind: crate::tsc::as_ts_script_kind(doc.media_type()),
version: state.script_version(&specifier),
}))
}
#[op]
@ -3312,10 +3315,9 @@ fn op_resolve(
}
#[op]
fn op_respond(state: &mut OpState, args: Response) -> bool {
fn op_respond(state: &mut OpState, args: Response) {
let state = state.borrow_mut::<State>();
state.response = Some(args);
true
}
#[op]

View file

@ -733,12 +733,9 @@ struct RespondArgs {
}
#[op]
fn op_respond(state: &mut OpState, args: Value) -> Result<Value, AnyError> {
fn op_respond(state: &mut OpState, args: RespondArgs) {
let state = state.borrow_mut::<State>();
let v: RespondArgs = serde_json::from_value(args)
.context("Error converting the result for \"op_respond\".")?;
state.maybe_response = Some(v);
Ok(json!(true))
state.maybe_response = Some(args);
}
/// Execute a request on the supplied snapshot, returning a response which
@ -1160,21 +1157,18 @@ mod tests {
#[tokio::test]
async fn test_respond() {
let mut state = setup(None, None, None).await;
let actual = op_respond::call(
&mut state,
json!({
"diagnostics": [
{
"messageText": "Unknown compiler option 'invalid'.",
"category": 1,
"code": 5023
}
],
"stats": [["a", 12]]
}),
)
.expect("should have invoked op");
assert_eq!(actual, json!(true));
let args = serde_json::from_value(json!({
"diagnostics": [
{
"messageText": "Unknown compiler option 'invalid'.",
"category": 1,
"code": 5023
}
],
"stats": [["a", 12]]
}))
.unwrap();
op_respond::call(&mut state, args);
let state = state.borrow::<State>();
assert_eq!(
state.maybe_response,