Skip to content

Commit 40122d7

Browse files
authored
fix(lsp): force correct media type detection from tsc (#20562)
1 parent 9004117 commit 40122d7

File tree

2 files changed

+82
-22
lines changed

2 files changed

+82
-22
lines changed

cli/lsp/tsc.rs

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::tsc::ResolveArgs;
3131
use crate::util::path::relative_specifier;
3232
use crate::util::path::specifier_to_file_path;
3333

34+
use deno_ast::MediaType;
3435
use deno_core::anyhow::anyhow;
3536
use deno_core::error::custom_error;
3637
use deno_core::error::AnyError;
@@ -3167,7 +3168,8 @@ struct State {
31673168
performance: Arc<Performance>,
31683169
response: Option<Response>,
31693170
state_snapshot: Arc<StateSnapshot>,
3170-
specifiers: HashMap<String, String>,
3171+
normalized_specifiers: HashMap<String, ModuleSpecifier>,
3172+
denormalized_specifiers: HashMap<ModuleSpecifier, String>,
31713173
token: CancellationToken,
31723174
}
31733175

@@ -3181,36 +3183,55 @@ impl State {
31813183
performance,
31823184
response: None,
31833185
state_snapshot,
3184-
specifiers: HashMap::default(),
3186+
normalized_specifiers: HashMap::default(),
3187+
denormalized_specifiers: HashMap::default(),
31853188
token: Default::default(),
31863189
}
31873190
}
31883191

3189-
/// If a normalized version of the specifier has been stored for tsc, this
3190-
/// will "restore" it for communicating back to the tsc language server,
3191-
/// otherwise it will just convert the specifier to a string.
3192-
fn denormalize_specifier(&self, specifier: &ModuleSpecifier) -> String {
3193-
let specifier_str = specifier.to_string();
3194-
self
3195-
.specifiers
3196-
.get(&specifier_str)
3197-
.unwrap_or(&specifier_str)
3198-
.to_string()
3192+
/// Convert the specifier to one compatible with tsc. Cache the resulting
3193+
/// mapping in case it needs to be reversed.
3194+
fn denormalize_specifier(&mut self, specifier: &ModuleSpecifier) -> String {
3195+
let original = specifier;
3196+
if let Some(specifier) = self.denormalized_specifiers.get(original) {
3197+
return specifier.to_string();
3198+
}
3199+
let mut specifier = original.to_string();
3200+
let media_type = MediaType::from_specifier(original);
3201+
// If the URL-inferred media type doesn't correspond to tsc's path-inferred
3202+
// media type, force it to be the same by appending an extension.
3203+
if MediaType::from_path(Path::new(specifier.as_str())) != media_type {
3204+
specifier += media_type.as_ts_extension();
3205+
}
3206+
if specifier != original.as_str() {
3207+
self
3208+
.normalized_specifiers
3209+
.insert(specifier.clone(), original.clone());
3210+
}
3211+
specifier
31993212
}
32003213

3201-
/// In certain situations, tsc can request "invalid" specifiers and this will
3202-
/// normalize and memoize the specifier.
3214+
/// Convert the specifier from one compatible with tsc. Cache the resulting
3215+
/// mapping in case it needs to be reversed.
32033216
fn normalize_specifier<S: AsRef<str>>(
32043217
&mut self,
32053218
specifier: S,
32063219
) -> Result<ModuleSpecifier, AnyError> {
3207-
let specifier_str = specifier.as_ref().replace(".d.ts.d.ts", ".d.ts");
3208-
if specifier_str != specifier.as_ref() {
3220+
let original = specifier.as_ref();
3221+
if let Some(specifier) = self.normalized_specifiers.get(original) {
3222+
return Ok(specifier.clone());
3223+
}
3224+
let specifier_str = original.replace(".d.ts.d.ts", ".d.ts");
3225+
let specifier = match ModuleSpecifier::parse(&specifier_str) {
3226+
Ok(s) => s,
3227+
Err(err) => return Err(err.into()),
3228+
};
3229+
if specifier.as_str() != original {
32093230
self
3210-
.specifiers
3211-
.insert(specifier_str.clone(), specifier.as_ref().to_string());
3231+
.denormalized_specifiers
3232+
.insert(specifier.clone(), original.to_string());
32123233
}
3213-
ModuleSpecifier::parse(&specifier_str).map_err(|err| err.into())
3234+
Ok(specifier)
32143235
}
32153236

32163237
fn get_asset_or_document(
@@ -3324,7 +3345,12 @@ fn op_resolve(
33243345
resolved
33253346
.into_iter()
33263347
.map(|o| {
3327-
o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string()))
3348+
o.map(|(s, mt)| {
3349+
(
3350+
state.denormalize_specifier(&s),
3351+
mt.as_ts_extension().to_string(),
3352+
)
3353+
})
33283354
})
33293355
.collect(),
33303356
)
@@ -3861,7 +3887,7 @@ enum RequestMethod {
38613887
}
38623888

38633889
impl RequestMethod {
3864-
fn to_value(&self, state: &State, id: usize) -> Value {
3890+
fn to_value(&self, state: &mut State, id: usize) -> Value {
38653891
match self {
38663892
RequestMethod::Configure(config) => json!({
38673893
"id": id,

cli/tests/integration/lsp_tests.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7847,6 +7847,40 @@ fn lsp_json_no_diagnostics() {
78477847
client.shutdown();
78487848
}
78497849

7850+
#[test]
7851+
fn lsp_json_import_with_query_string() {
7852+
let context = TestContextBuilder::new().use_temp_cwd().build();
7853+
let temp_dir = context.temp_dir();
7854+
temp_dir.write("data.json", r#"{"k": "v"}"#);
7855+
temp_dir.write(
7856+
"main.ts",
7857+
r#"
7858+
import data from "./data.json?1" with { type: "json" };
7859+
console.log(data);
7860+
"#,
7861+
);
7862+
let mut client = context.new_lsp_command().build();
7863+
client.initialize_default();
7864+
client.did_open(json!({
7865+
"textDocument": {
7866+
"uri": temp_dir.uri().join("data.json").unwrap(),
7867+
"languageId": "json",
7868+
"version": 1,
7869+
"text": temp_dir.read_to_string("data.json"),
7870+
}
7871+
}));
7872+
let diagnostics = client.did_open(json!({
7873+
"textDocument": {
7874+
"uri": temp_dir.uri().join("main.ts").unwrap(),
7875+
"languageId": "typescript",
7876+
"version": 1,
7877+
"text": temp_dir.read_to_string("main.ts"),
7878+
}
7879+
}));
7880+
assert_eq!(diagnostics.all(), vec![]);
7881+
client.shutdown();
7882+
}
7883+
78507884
#[test]
78517885
fn lsp_format_markdown() {
78527886
let context = TestContextBuilder::new().use_temp_cwd().build();
@@ -9198,7 +9232,7 @@ fn lsp_data_urls_with_jsx_compiler_option() {
91989232
"end": { "line": 1, "character": 1 }
91999233
}
92009234
}, {
9201-
"uri": "deno:/ed0224c51f7e2a845dfc0941ed6959675e5e3e3d2a39b127f0ff569c1ffda8d8/data_url.ts",
9235+
"uri": "deno:/5c42b5916c4a3fb55be33fdb0c3b1f438639420592d150fca1b6dc043c1df3d9/data_url.ts",
92029236
"range": {
92039237
"start": { "line": 0, "character": 7 },
92049238
"end": {"line": 0, "character": 14 },

0 commit comments

Comments
 (0)