1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 08:39:09 -05:00

fix(check): should bust check cache when json module or npm resolution changes (#19941)

A small part of #19928.
This commit is contained in:
David Sherret 2023-07-26 17:23:07 -04:00 committed by GitHub
parent 53e077133f
commit cf16df00d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 223 additions and 58 deletions

26
Cargo.lock generated
View file

@ -1035,9 +1035,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_doc" name = "deno_doc"
version = "0.63.1" version = "0.64.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "499936300106c3c67caae87e29def3df5ea9385db6ed7428f154972f70ed39fa" checksum = "c25d38dc94e4c2190bc7a1851790dd142fd0068888b3a26a1acb958db5113829"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"deno_ast", "deno_ast",
@ -1053,9 +1053,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_emit" name = "deno_emit"
version = "0.24.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdc024d2c1e5ec56ef6f923be2c2fea4662d596b0a68074ccf89991b38a05e7" checksum = "0ef00ad78bc75e7f35a01bd4b5c32a97018eebc55c311e4b287e158a29021348"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.13.1", "base64 0.13.1",
@ -1121,9 +1121,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_graph" name = "deno_graph"
version = "0.49.0" version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7e07fdff6c7dc1a9a7c03ce69435fda4b53641d2d6d3d3ed6d29cf67fefd3ea" checksum = "73502c4d93a17f259b6edee6d5a5ba063e2fcdcdaeb6ca1c6953129cc14be6a7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"data-url", "data-url",
@ -1330,9 +1330,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_npm" name = "deno_npm"
version = "0.9.1" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "371ef0398b5b5460d66b78a958d5015658e198ad3a29fb9ce329459272fd29aa" checksum = "fa5d1097de53e8ce3316d3e44095e253719ae367cf7478263f83082f44dddabf"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1340,7 +1340,6 @@ dependencies = [
"futures", "futures",
"log", "log",
"monch", "monch",
"once_cell",
"serde", "serde",
"thiserror", "thiserror",
] ]
@ -1424,11 +1423,12 @@ dependencies = [
[[package]] [[package]]
name = "deno_semver" name = "deno_semver"
version = "0.2.2" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "242c8ad9f4ce614ec0fa2e6b3d834f2662ce024ca78e9ed4c58d812cbfc3e41d" checksum = "96f99990457915af1f444900003ffd5a9d3ab2e5337b06d681e56ca371b3e11f"
dependencies = [ dependencies = [
"monch", "monch",
"once_cell",
"serde", "serde",
"thiserror", "thiserror",
"url", "url",
@ -1944,9 +1944,9 @@ dependencies = [
[[package]] [[package]]
name = "eszip" name = "eszip"
version = "0.45.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbf5a0f47c2e73cb7631accc282ebda9bc9ed9b2034abfddec983dc9c8f78e7a" checksum = "58d2382a70396ccffd260adb879ed9f954c45457e8075830c7d4ec30c6dc8ad2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.21.0", "base64 0.21.0",

View file

@ -51,8 +51,8 @@ deno_bench_util = { version = "0.107.0", path = "./bench_util" }
test_util = { path = "./test_util" } test_util = { path = "./test_util" }
deno_lockfile = "0.14.1" deno_lockfile = "0.14.1"
deno_media_type = { version = "0.1.0", features = ["module_specifier"] } deno_media_type = { version = "0.1.0", features = ["module_specifier"] }
deno_npm = "0.9.1" deno_npm = "0.10.1"
deno_semver = "0.2.2" deno_semver = "0.3.0"
# exts # exts
deno_broadcast_channel = { version = "0.107.0", path = "./ext/broadcast_channel" } deno_broadcast_channel = { version = "0.107.0", path = "./ext/broadcast_channel" }

View file

@ -42,16 +42,16 @@ winres.workspace = true
[dependencies] [dependencies]
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = "=0.63.1" deno_doc = "=0.64.0"
deno_emit = "=0.24.0" deno_emit = "=0.25.0"
deno_graph = "=0.49.0" deno_graph = "=0.50.0"
deno_lint = { version = "=0.49.0", features = ["docs"] } deno_lint = { version = "=0.49.0", features = ["docs"] }
deno_lockfile.workspace = true deno_lockfile.workspace = true
deno_npm.workspace = true deno_npm.workspace = true
deno_runtime = { workspace = true, features = ["dont_create_runtime_snapshot", "include_js_files_for_snapshotting"] } deno_runtime = { workspace = true, features = ["dont_create_runtime_snapshot", "include_js_files_for_snapshotting"] }
deno_semver.workspace = true deno_semver.workspace = true
deno_task_shell = "=0.13.1" deno_task_shell = "=0.13.1"
eszip = "=0.45.0" eszip = "=0.48.0"
napi_sym.workspace = true napi_sym.workspace = true
async-trait.workspace = true async-trait.workspace = true

View file

@ -85,7 +85,7 @@ pub fn get_local_package_json_version_reqs(
match result { match result {
Ok(version_req) => Ok(NpmPackageReq { Ok(version_req) => Ok(NpmPackageReq {
name: name.to_string(), name: name.to_string(),
version_req: Some(version_req), version_req,
}), }),
Err(err) => Err(PackageJsonDepValueParseError::Specifier(err)), Err(err) => Err(PackageJsonDepValueParseError::Specifier(err)),
} }

View file

@ -1,5 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
@ -249,6 +250,10 @@ impl NpmResolution {
Ok(nv) Ok(nv)
} }
pub fn package_reqs(&self) -> HashMap<NpmPackageReq, NpmPackageNv> {
self.snapshot.read().package_reqs().clone()
}
pub fn all_system_packages( pub fn all_system_packages(
&self, &self,
system_info: &NpmSystemInfo, system_info: &NpmSystemInfo,
@ -303,21 +308,20 @@ async fn add_package_reqs_to_snapshot(
get_new_snapshot: impl Fn() -> NpmResolutionSnapshot, get_new_snapshot: impl Fn() -> NpmResolutionSnapshot,
) -> Result<NpmResolutionSnapshot, AnyError> { ) -> Result<NpmResolutionSnapshot, AnyError> {
let snapshot = get_new_snapshot(); let snapshot = get_new_snapshot();
if !snapshot.has_pending() let snapshot = if !snapshot.has_pending()
&& package_reqs && package_reqs
.iter() .iter()
.all(|req| snapshot.package_reqs().contains_key(req)) .all(|req| snapshot.package_reqs().contains_key(req))
{ {
log::debug!("Snapshot already up to date. Skipping pending resolution."); log::debug!("Snapshot already up to date. Skipping pending resolution.");
return Ok(snapshot); snapshot
} } else {
let pending_resolver = get_npm_pending_resolver(api); let pending_resolver = get_npm_pending_resolver(api);
let result = pending_resolver let result = pending_resolver
.resolve_pending(snapshot, package_reqs) .resolve_pending(snapshot, package_reqs)
.await; .await;
api.clear_memory_cache(); api.clear_memory_cache();
let snapshot = match result { match result {
Ok(snapshot) => snapshot, Ok(snapshot) => snapshot,
Err(NpmResolutionError::Resolution(err)) if api.mark_force_reload() => { Err(NpmResolutionError::Resolution(err)) if api.mark_force_reload() => {
log::debug!("{err:#}"); log::debug!("{err:#}");
@ -333,15 +337,15 @@ async fn add_package_reqs_to_snapshot(
result? result?
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}
}; };
if let Some(lockfile_mutex) = maybe_lockfile { if let Some(lockfile_mutex) = maybe_lockfile {
let mut lockfile = lockfile_mutex.lock(); let mut lockfile = lockfile_mutex.lock();
populate_lockfile_from_snapshot(&mut lockfile, &snapshot)?; populate_lockfile_from_snapshot(&mut lockfile, &snapshot)?;
Ok(snapshot)
} else {
Ok(snapshot)
} }
Ok(snapshot)
} }
fn get_npm_pending_resolver( fn get_npm_pending_resolver(

View file

@ -4,6 +4,7 @@ mod common;
mod global; mod global;
mod local; mod local;
use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -229,6 +230,10 @@ return Ok(None);
.unwrap() .unwrap()
} }
pub fn package_reqs(&self) -> HashMap<NpmPackageReq, NpmPackageNv> {
self.resolution.package_reqs()
}
pub fn snapshot(&self) -> NpmResolutionSnapshot { pub fn snapshot(&self) -> NpmResolutionSnapshot {
self.resolution.snapshot() self.resolution.snapshot()
} }

View file

@ -191,7 +191,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
} }
let module = module?; let module = module?;
let code = module.source().await.unwrap_or_default(); let code = module.source().await.unwrap_or_else(|| Arc::new([]));
let code = std::str::from_utf8(&code) let code = std::str::from_utf8(&code)
.map_err(|_| type_error("Module source is not utf-8"))? .map_err(|_| type_error("Module source is not utf-8"))?
.to_owned() .to_owned()
@ -204,6 +204,9 @@ impl ModuleLoader for EmbeddedModuleLoader {
eszip::ModuleKind::Jsonc => { eszip::ModuleKind::Jsonc => {
return Err(type_error("jsonc modules not supported")) return Err(type_error("jsonc modules not supported"))
} }
eszip::ModuleKind::OpaqueData => {
unreachable!();
}
}, },
code, code,
&module_specifier, &module_specifier,

View file

@ -307,3 +307,80 @@ fn check_error_in_dep_then_fix() {
output.assert_matches_text("Check [WILDCARD]main.ts\nerror: TS234[WILDCARD]"); output.assert_matches_text("Check [WILDCARD]main.ts\nerror: TS234[WILDCARD]");
output.assert_exit_code(1); output.assert_exit_code(1);
} }
#[test]
fn json_module_check_then_error() {
let test_context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = test_context.temp_dir();
let correct_code = "{ \"foo\": \"bar\" }";
let incorrect_code = "{ \"foo2\": \"bar\" }";
temp_dir.write(
"main.ts",
"import test from './test.json' assert { type: 'json' }; console.log(test.foo);\n",
);
temp_dir.write("test.json", correct_code);
let check_command = test_context.new_command().args_vec(["check", "main.ts"]);
check_command.run().assert_exit_code(0).skip_output_check();
temp_dir.write("test.json", incorrect_code);
check_command
.run()
.assert_matches_text("Check [WILDCARD]main.ts\nerror: TS2551[WILDCARD]")
.assert_exit_code(1);
}
#[test]
fn npm_module_check_then_error() {
let test_context = TestContextBuilder::new()
.use_temp_cwd()
.add_npm_env_vars()
.use_http_server()
.build();
let temp_dir = test_context.temp_dir();
temp_dir.write("deno.json", "{}"); // so the lockfile gets loaded
// get the lockfiles values first (this is necessary because the test
// server generates different tarballs based on the operating system)
test_context
.new_command()
.args_vec([
"cache",
"npm:@denotest/breaking-change-between-versions@1.0.0",
"npm:@denotest/breaking-change-between-versions@2.0.0",
])
.run()
.skip_output_check();
let lockfile = temp_dir.path().join("deno.lock");
let mut lockfile_content =
lockfile.read_json::<deno_lockfile::LockfileContent>();
// make the specifier resolve to version 1
lockfile_content.npm.specifiers.insert(
"@denotest/breaking-change-between-versions".to_string(),
"@denotest/breaking-change-between-versions@1.0.0".to_string(),
);
lockfile.write_json(&lockfile_content);
temp_dir.write(
"main.ts",
"import { oldName } from 'npm:@denotest/breaking-change-between-versions'; console.log(oldName());\n",
);
let check_command = test_context.new_command().args_vec(["check", "main.ts"]);
check_command.run().assert_exit_code(0).skip_output_check();
// now update the lockfile to use version 2 instead, which should cause a
// type checking error because the oldName no longer exists
lockfile_content.npm.specifiers.insert(
"@denotest/breaking-change-between-versions".to_string(),
"@denotest/breaking-change-between-versions@2.0.0".to_string(),
);
lockfile.write_json(&lockfile_content);
check_command
.run()
.assert_matches_text("Check [WILDCARD]main.ts\nerror: TS2305[WILDCARD]has no exported member 'oldName'[WILDCARD]")
.assert_exit_code(1);
}

View file

@ -0,0 +1 @@
export function oldName(): 1;

View file

@ -0,0 +1,3 @@
export function newName() {
return 1;
}

View file

@ -0,0 +1,6 @@
{
"name": "@denotest/breaking-change-between-versions",
"version": "1.0.0",
"type": "module",
"main": "index.js"
}

View file

@ -0,0 +1 @@
export function newName(): 2;

View file

@ -0,0 +1,3 @@
export function newName() {
return 2;
}

View file

@ -0,0 +1,6 @@
{
"name": "@denotest/breaking-change-between-versions",
"version": "2.0.0",
"type": "module",
"main": "index.js"
}

View file

@ -1,5 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
@ -10,6 +11,8 @@ use deno_graph::Module;
use deno_graph::ModuleGraph; use deno_graph::ModuleGraph;
use deno_runtime::colors; use deno_runtime::colors;
use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::NodeResolver;
use deno_semver::npm::NpmPackageNv;
use deno_semver::npm::NpmPackageReq;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
@ -93,7 +96,12 @@ impl TypeChecker {
let debug = self.cli_options.log_level() == Some(log::Level::Debug); let debug = self.cli_options.log_level() == Some(log::Level::Debug);
let cache = TypeCheckCache::new(self.caches.type_checking_cache_db()); let cache = TypeCheckCache::new(self.caches.type_checking_cache_db());
let check_js = ts_config.get_check_js(); let check_js = ts_config.get_check_js();
let check_hash = match get_check_hash(&graph, type_check_mode, &ts_config) { let check_hash = match get_check_hash(
&graph,
self.npm_resolver.package_reqs(),
type_check_mode,
&ts_config,
) {
CheckHashResult::NoFiles => return Ok(()), CheckHashResult::NoFiles => return Ok(()),
CheckHashResult::Hash(hash) => hash, CheckHashResult::Hash(hash) => hash,
}; };
@ -186,6 +194,7 @@ enum CheckHashResult {
/// be used to tell /// be used to tell
fn get_check_hash( fn get_check_hash(
graph: &ModuleGraph, graph: &ModuleGraph,
package_reqs: HashMap<NpmPackageReq, NpmPackageNv>,
type_check_mode: TypeCheckMode, type_check_mode: TypeCheckMode,
ts_config: &TsConfig, ts_config: &TsConfig,
) -> CheckHashResult { ) -> CheckHashResult {
@ -198,11 +207,10 @@ fn get_check_hash(
hasher.write(&ts_config.as_bytes()); hasher.write(&ts_config.as_bytes());
let check_js = ts_config.get_check_js(); let check_js = ts_config.get_check_js();
let mut sorted_modules = graph.modules().collect::<Vec<_>>();
sorted_modules.sort_by_key(|m| m.specifier().as_str()); // make it deterministic
let mut has_file = false; let mut has_file = false;
let mut has_file_to_type_check = false; let mut has_file_to_type_check = false;
for module in sorted_modules { // this iterator of modules is already deterministic, so no need to sort it
for module in graph.modules() {
match module { match module {
Module::Esm(module) => { Module::Esm(module) => {
let ts_check = has_ts_check(module.media_type, &module.source); let ts_check = has_ts_check(module.media_type, &module.source);
@ -240,15 +248,36 @@ fn get_check_hash(
hasher.write_str(module.specifier.as_str()); hasher.write_str(module.specifier.as_str());
hasher.write_str(&module.source); hasher.write_str(&module.source);
} }
Module::Json(_) Module::Node(_) => {
| Module::External(_) // the @types/node package will be in the resolved
| Module::Node(_) // snapshot below so don't bother including it here
| Module::Npm(_) => { }
// ignore Module::Npm(_) => {
// don't bother adding this specifier to the hash
// because what matters is the resolved npm snapshot,
// which is hashed below
}
Module::Json(module) => {
has_file_to_type_check = true;
hasher.write_str(module.specifier.as_str());
hasher.write_str(&module.source);
}
Module::External(module) => {
hasher.write_str(module.specifier.as_str());
} }
} }
} }
// Check if any of the top level npm pckages have changed. We could go
// further and check all the individual npm packages, but that's
// probably overkill.
let mut package_reqs = package_reqs.into_iter().collect::<Vec<_>>();
package_reqs.sort_by(|a, b| a.0.cmp(&b.0)); // determinism
for (pkg_req, pkg_nv) in package_reqs {
hasher.write_hashable(&pkg_req);
hasher.write_hashable(&pkg_nv);
}
if !has_file || !check_js && !has_file_to_type_check { if !has_file || !check_js && !has_file_to_type_check {
// no files to type check // no files to type check
CheckHashResult::NoFiles CheckHashResult::NoFiles

View file

@ -28,6 +28,7 @@ use deno_core::ModuleSpecifier;
use deno_core::OpState; use deno_core::OpState;
use deno_core::RuntimeOptions; use deno_core::RuntimeOptions;
use deno_core::Snapshot; use deno_core::Snapshot;
use deno_graph::GraphKind;
use deno_graph::Module; use deno_graph::Module;
use deno_graph::ModuleGraph; use deno_graph::ModuleGraph;
use deno_graph::ResolutionResolved; use deno_graph::ResolutionResolved;
@ -319,7 +320,7 @@ pub struct Response {
pub stats: Stats, pub stats: Stats,
} }
#[derive(Debug, Default)] #[derive(Debug)]
struct State { struct State {
hash_data: u64, hash_data: u64,
graph: Arc<ModuleGraph>, graph: Arc<ModuleGraph>,
@ -331,6 +332,21 @@ struct State {
current_dir: PathBuf, current_dir: PathBuf,
} }
impl Default for State {
fn default() -> Self {
Self {
hash_data: Default::default(),
graph: Arc::new(ModuleGraph::new(GraphKind::All)),
maybe_tsbuildinfo: Default::default(),
maybe_response: Default::default(),
maybe_node_resolver: Default::default(),
remapped_specifiers: Default::default(),
root_map: Default::default(),
current_dir: Default::default(),
}
}
}
impl State { impl State {
pub fn new( pub fn new(
graph: Arc<ModuleGraph>, graph: Arc<ModuleGraph>,

View file

@ -10,6 +10,8 @@ use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
use lsp_types::Url; use lsp_types::Url;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::assertions::assert_wildcard_match; use crate::assertions::assert_wildcard_match;
@ -110,6 +112,10 @@ impl PathRef {
.with_context(|| format!("Could not read file: {}", self)) .with_context(|| format!("Could not read file: {}", self))
} }
pub fn read_json<TValue: DeserializeOwned>(&self) -> TValue {
serde_json::from_str(&self.read_to_string()).unwrap()
}
pub fn rename(&self, to: impl AsRef<Path>) { pub fn rename(&self, to: impl AsRef<Path>) {
fs::rename(self, self.join(to)).unwrap(); fs::rename(self, self.join(to)).unwrap();
} }
@ -118,6 +124,11 @@ impl PathRef {
fs::write(self, text.as_ref()).unwrap(); fs::write(self, text.as_ref()).unwrap();
} }
pub fn write_json<TValue: Serialize>(&self, value: &TValue) {
let text = serde_json::to_string_pretty(value).unwrap();
self.write(text);
}
pub fn symlink_dir( pub fn symlink_dir(
&self, &self,
oldpath: impl AsRef<Path>, oldpath: impl AsRef<Path>,