diff --git a/.dprint.json b/.dprint.json index a1e4e10181..2bc038e933 100644 --- a/.dprint.json +++ b/.dprint.json @@ -56,7 +56,7 @@ "ext/websocket/autobahn/reports" ], "plugins": [ - "https://plugins.dprint.dev/typescript-0.88.10.wasm", + "https://plugins.dprint.dev/typescript-0.89.0.wasm", "https://plugins.dprint.dev/json-0.19.1.wasm", "https://plugins.dprint.dev/markdown-0.16.3.wasm", "https://plugins.dprint.dev/toml-0.6.0.wasm", diff --git a/Cargo.lock b/Cargo.lock index 9aa4198926..e6dd24dbcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -1071,14 +1072,17 @@ dependencies = [ [[package]] name = "deno_ast" -version = "0.32.1" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa239d4d69bb6c61bd73e0fc23e3688c7e87e1f47f2f37f4cff7a0080017299" +checksum = "9fdafff817ae3ad89672d54cd8daebc86dc352065ccc18691605043e6b845d00" dependencies = [ "anyhow", "base64", "deno_media_type", + "deno_terminal", "dprint-swc-ext", + "once_cell", + "percent-encoding", "serde", "swc_atoms", "swc_bundler", @@ -1088,7 +1092,6 @@ dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", "swc_ecma_codegen_macros", - "swc_ecma_dep_graph", "swc_ecma_loader", "swc_ecma_parser", "swc_ecma_transforms_base", @@ -1106,6 +1109,7 @@ dependencies = [ "swc_visit", "swc_visit_macros", "text_lines", + "unicode-width", "url", ] @@ -1281,9 +1285,9 @@ dependencies = [ [[package]] name = "deno_doc" -version = "0.100.0" +version = "0.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1df9ba70ba4335847c304a9a771da4833e4e0c219758b8b58db36c096061b7b" +checksum = "73fe6bd8144456ca3f01b8d1cd1b668b974c84dc94cb642936c0938348b17017" dependencies = [ "anyhow", "cfg-if", @@ -1305,9 +1309,9 @@ dependencies = [ [[package]] name = "deno_emit" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870bd633969034668194c6cdf7d6f8aa94296e26db554aba1ea5f074aa966c37" +checksum = "3c5002f2c25489fb993132dc0cb0dabd41bae70a8629168db4bd726ee2e296ac" dependencies = [ "anyhow", "base64", @@ -1375,9 +1379,9 @@ dependencies = [ [[package]] name = "deno_graph" -version = "0.64.2" +version = "0.65.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a60def166df99195520b3b020974fdf7d3865b7a07eca5284ff0d24235f972da" +checksum = "bd7e17cc32255286c37c81a44425b71c4a7e1aadb7bdf65968a31d28415fa1d0" dependencies = [ "anyhow", "async-trait", @@ -1485,9 +1489,9 @@ dependencies = [ [[package]] name = "deno_lint" -version = "0.55.2" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a23713fe2da1e320e95f6b6d137b00b75554dfe3018ebc89bc7922b0dae37d8" +checksum = "0367f164f601211ea2593937d1b73c0f46ab966ab0f7b16d4eb1d7582a7be401" dependencies = [ "anyhow", "deno_ast", @@ -1755,9 +1759,9 @@ dependencies = [ [[package]] name = "deno_terminal" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b847702ef57565e1417fad2291f65a3c8a5ecf2ba38d64e56f02828e7546d891" +checksum = "7e6337d4e7f375f8b986409a76fbeecfa4bd8a1343e63355729ae4befa058eaf" dependencies = [ "once_cell", "termcolor", @@ -2158,22 +2162,23 @@ dependencies = [ [[package]] name = "dprint-plugin-typescript" -version = "0.88.10" +version = "0.89.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4df63bcc9131ef68094ed468bf7b252e5771ed120545ea42983e99e411d467" +checksum = "7dedd02b402282e71c309aa3e1e27bc8557517c968effe52783cdeeafdd17cc2" dependencies = [ "anyhow", "deno_ast", "dprint-core", + "percent-encoding", "rustc-hash", "serde", ] [[package]] name = "dprint-swc-ext" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f24ce6b89a06ae3eb08d5d4f88c05d0aef1fa58e2eba8dd92c97b84210c25" +checksum = "ebaedd46a16dd179b260a9fcb56be5780814afcb20f615eedde6acf971c9628e" dependencies = [ "bumpalo", "num-bigint", @@ -2383,9 +2388,9 @@ dependencies = [ [[package]] name = "eszip" -version = "0.60.0" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a060f8bb81229bd98c26e1c0efc066be2460558ee9187e73e40a89bd2c949f06" +checksum = "7a26aa6791e6021e9e3ffc6bc8ab00ff2d0d748c64a75b7333076d973ce32f6b" dependencies = [ "anyhow", "base64", @@ -4343,9 +4348,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" @@ -5741,9 +5746,9 @@ dependencies = [ [[package]] name = "swc_bundler" -version = "0.223.20" +version = "0.225.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7530df85b1a56f6a879ca102dc59718db4bcd6bfff55fb8bb379fbeab6c88c" +checksum = "26491762e84ae1d0a2e179fe48066072834777a1b12e8e88a7f07c8f92cc0188" dependencies = [ "anyhow", "crc", @@ -5770,10 +5775,24 @@ dependencies = [ ] [[package]] -name = "swc_common" -version = "0.33.12" +name = "swc_cached" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3ae36feceded27f0178dc9dabb49399830847ffb7f866af01798844de8f973" +checksum = "630c761c74ac8021490b78578cc2223aa4a568241e26505c27bf0e4fd4ad8ec2" +dependencies = [ + "ahash", + "anyhow", + "dashmap", + "once_cell", + "regex", + "serde", +] + +[[package]] +name = "swc_common" +version = "0.33.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "095c158fe55b36faeebb4274692643a6d7cdc5b7902e1d5968ddbe52b7de1d1c" dependencies = [ "ast_node", "better_scoped_tls", @@ -5797,13 +5816,15 @@ dependencies = [ [[package]] name = "swc_config" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112884e66b60e614c0f416138b91b8b82b7fea6ed0ecc5e26bad4726c57a6c99" +checksum = "ce837c5eae1cb200a310940de989fd9b3d12ed62d7752bc69b39ef8aa775ec04" dependencies = [ + "anyhow", "indexmap", "serde", "serde_json", + "swc_cached", "swc_config_macro", ] @@ -5821,9 +5842,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.110.17" +version = "0.112.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79401a45da704f4fb2552c5bf86ee2198e8636b121cb81f8036848a300edd53b" +checksum = "852a48a24a2533de88298c6b25355bc68fdee31ac21cb4fb8939b7001715353c" dependencies = [ "bitflags 2.4.1", "is-macro", @@ -5839,9 +5860,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.146.54" +version = "0.148.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b61ca275e3663238b71c4b5da8e6fb745bde9989ef37d94984dfc81fc6d009" +checksum = "d79df3f8c5ed028fce5dc24acb83002c0854f8b9d7e893292aeee394a6b9eaf4" dependencies = [ "memchr", "num-bigint", @@ -5868,36 +5889,25 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "swc_ecma_dep_graph" -version = "0.113.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fc6ac1a84afe910182dcda33d70a16545e6058529d51afb63bd6be8764370f" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_visit", -] - [[package]] name = "swc_ecma_loader" -version = "0.45.13" +version = "0.45.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5713ab3429530c10bdf167170ebbde75b046c8003558459e4de5aaec62ce0f1" +checksum = "7c16051bce5421992a1b49350735bf4d110f761fd68ae7098af17a64ad639b8d" dependencies = [ "anyhow", "pathdiff", "serde", + "swc_atoms", "swc_common", "tracing", ] [[package]] name = "swc_ecma_parser" -version = "0.141.37" +version = "0.143.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d17401dd95048a6a62b777d533c0999dabdd531ef9d667e22f8ae2a2a0d294" +checksum = "90ff55811ed5de14b05e9a2979bae2bce3c807582f559b4325948463265307d9" dependencies = [ "either", "new_debug_unreachable", @@ -5917,9 +5927,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.135.11" +version = "0.137.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4ab26ec124b03e47f54d4daade8e9a9dcd66d3a4ca3cd47045f138d267a60e" +checksum = "bfd47dd9ccb73a1f5d8d7eff9518554b752b1733b56503af090e78859abb42dd" dependencies = [ "better_scoped_tls", "bitflags 2.4.1", @@ -5940,9 +5950,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.124.11" +version = "0.126.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fe4376c024fa04394cafb8faecafb4623722b92dbbe46532258cc0a6b569d9c" +checksum = "8ecb31417e0d415d7f0ff026f1e7c909427e386b7d0af9a2a78678507e4d9d79" dependencies = [ "swc_atoms", "swc_common", @@ -5966,9 +5976,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.196.17" +version = "0.198.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec185cf4d18e90b7c8b18b0d1f04a5707e6f4c7b57c1bfd5086392cd07b75a9" +checksum = "3920268ac8972b494067d0b7c088964b21d08f5d1f58d7151bd1eb7054a137b0" dependencies = [ "dashmap", "indexmap", @@ -5990,9 +6000,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.169.16" +version = "0.171.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed89d6ff74f60de490fb56e1cc505b057905e36c13d405d7d61dd5c9f6ee8fc9" +checksum = "448c40c2a2b224cb5101cc6cdee81837c281a34f2a2aa6dd18d6d5cd8d492e60" dependencies = [ "either", "rustc-hash", @@ -6010,9 +6020,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.181.18" +version = "0.183.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31a2f879fd21d18080b6c42e633e0ae8c6f3d54b83c1de876767d82b458c999" +checksum = "ee2394dc3abceada246feeb709b8c4d23392973f49a24fcc59b2ee21737cb6c8" dependencies = [ "base64", "dashmap", @@ -6034,9 +6044,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.186.17" +version = "0.188.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4263372cc7cd1a3b4570ccf7438f3c1e1575f134fd05cdf074edb322480a5b" +checksum = "0cff231437173e041e5a3be9b8c782fd297ffcb53ed16d805f853e4a68315c45" dependencies = [ "ryu-js", "serde", @@ -6051,9 +6061,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.125.4" +version = "0.127.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cead1083e46b0f072a82938f16d366014468f7510350957765bb4d013496890" +checksum = "4cd185161161dfc65ee0d6f3044c901b766c3abb4efcd0b35c9e76c833724896" dependencies = [ "indexmap", "num_cpus", @@ -6069,9 +6079,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.96.17" +version = "0.98.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d0100c383fb08b6f34911ab6f925950416a5d14404c1cd520d59fb8dfbb3bf" +checksum = "cdb71511a816c7c84ddc96e6939389be261caf20858486a5e76948551f110e1f" dependencies = [ "num-bigint", "swc_atoms", @@ -6094,9 +6104,9 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "0.21.13" +version = "0.21.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acfc056067a0fbfe26a4763c1eb246e813fdbe6b376415d07915e96e15481b6" +checksum = "ffd32eda2dd2c725f8d4448d0013c3b5466118e4ff5c30aff2c04f6750f7238b" dependencies = [ "indexmap", "petgraph", @@ -6106,9 +6116,9 @@ dependencies = [ [[package]] name = "swc_graph_analyzer" -version = "0.22.15" +version = "0.22.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6e0110c0433c27221f03e45419b7e18d1db4d472db309088caa458ac2f304e" +checksum = "52ae1172960aa3b0cdbe94a1d5edf3efa9f1199cbd8384f48dedd0c5bdb5d6bd" dependencies = [ "auto_impl", "petgraph", diff --git a/Cargo.toml b/Cargo.toml index f9e2f52195..505364efe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,14 +41,14 @@ license = "MIT" repository = "https://github.com/denoland/deno" [workspace.dependencies] -deno_ast = { version = "0.32.0", features = ["transpiling"] } +deno_ast = { version = "0.33.2", features = ["transpiling"] } deno_core = { version = "0.260.0" } deno_bench_util = { version = "0.131.0", path = "./bench_util" } deno_lockfile = "0.18.0" deno_media_type = { version = "0.1.1", features = ["module_specifier"] } deno_runtime = { version = "0.145.0", path = "./runtime" } -deno_terminal = "0.1.0" +deno_terminal = "0.1.1" napi_sym = { version = "0.67.0", path = "./cli/napi/sym" } test_util = { path = "./test_util" } @@ -131,7 +131,7 @@ p224 = { version = "0.13.0", features = ["ecdh"] } p256 = { version = "0.13.2", features = ["ecdh"] } p384 = { version = "0.13.0", features = ["ecdh"] } parking_lot = "0.12.0" -percent-encoding = "=2.3.0" +percent-encoding = "2.3.0" phf = { version = "0.11", features = ["macros"] } pin-project = "1.0.11" # don't pin because they yank crates from cargo pretty_assertions = "=1.4.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index fdef096ade..dffce000a7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -53,21 +53,21 @@ winapi.workspace = true winres.workspace = true [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", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } deno_cache_dir = "=0.6.1" deno_config = "=0.9.2" deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } -deno_doc = { version = "=0.100.0", features = ["html"] } -deno_emit = "=0.35.0" -deno_graph = "=0.64.2" -deno_lint = { version = "=0.55.2", features = ["docs"] } +deno_doc = { version = "=0.103.0", features = ["html"] } +deno_emit = "=0.36.0" +deno_graph = "=0.65.0" +deno_lint = { version = "=0.56.0", features = ["docs"] } deno_lockfile.workspace = true deno_npm = "=0.16.0" deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_semver = "=0.5.4" deno_task_shell = "=0.14.3" deno_terminal.workspace = true -eszip = "=0.60.0" +eszip = "=0.62.0" napi_sym.workspace = true async-trait.workspace = true @@ -89,7 +89,7 @@ dotenvy = "0.15.7" dprint-plugin-json = "=0.19.1" dprint-plugin-jupyter = "=0.1.2" dprint-plugin-markdown = "=0.16.3" -dprint-plugin-typescript = "=0.88.10" +dprint-plugin-typescript = "=0.89.0" env_logger = "=0.10.0" fancy-regex = "=0.10.0" # If you disable the default __vendored_zlib_ng feature above, you _must_ be able to link against `-lz`. diff --git a/cli/cache/module_info.rs b/cli/cache/module_info.rs index 063809f849..6bb7180389 100644 --- a/cli/cache/module_info.rs +++ b/cli/cache/module_info.rs @@ -149,7 +149,7 @@ impl<'a> deno_graph::ModuleAnalyzer for ModuleInfoCacheModuleAnalyzer<'a> { specifier: &ModuleSpecifier, source: Arc, media_type: MediaType, - ) -> Result { + ) -> Result { // attempt to load from the cache let source_hash = ModuleInfoCacheSourceHash::from_source(source.as_bytes()); match self.module_info_cache.get_module_info( diff --git a/cli/cache/parsed_source.rs b/cli/cache/parsed_source.rs index 75170aaf96..8d98587e2f 100644 --- a/cli/cache/parsed_source.rs +++ b/cli/cache/parsed_source.rs @@ -32,7 +32,7 @@ impl<'a> LazyGraphSourceParser<'a> { pub fn get_or_parse_source( &self, module_specifier: &ModuleSpecifier, - ) -> Result, deno_ast::Diagnostic> { + ) -> Result, deno_ast::ParseDiagnostic> { let Some(deno_graph::Module::Js(module)) = self.graph.get(module_specifier) else { return Ok(None); @@ -53,7 +53,7 @@ impl ParsedSourceCache { pub fn get_parsed_source_from_js_module( &self, module: &deno_graph::JsModule, - ) -> Result { + ) -> Result { self.get_or_parse_module( &module.specifier, module.source.clone(), @@ -68,7 +68,7 @@ impl ParsedSourceCache { specifier: &deno_graph::ModuleSpecifier, source: Arc, media_type: MediaType, - ) -> deno_core::anyhow::Result { + ) -> deno_core::anyhow::Result { let parser = self.as_capturing_parser(); // this will conditionally parse because it's using a CapturingModuleParser parser.parse_module(ParseOptions { diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs deleted file mode 100644 index 7eff66d768..0000000000 --- a/cli/diagnostics.rs +++ /dev/null @@ -1,678 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::borrow::Cow; -use std::fmt; -use std::fmt::Display; -use std::fmt::Write as _; -use std::path::PathBuf; - -use deno_ast::ModuleSpecifier; -use deno_ast::SourcePos; -use deno_ast::SourceRange; -use deno_ast::SourceRanged; -use deno_ast::SourceTextInfo; -use deno_terminal::colors; -use unicode_width::UnicodeWidthStr; - -use crate::cache::LazyGraphSourceParser; - -pub trait SourceTextStore { - fn get_source_text<'a>( - &'a self, - specifier: &ModuleSpecifier, - ) -> Option>; -} - -pub struct SourceTextParsedSourceStore<'a>(pub LazyGraphSourceParser<'a>); - -impl<'a> SourceTextParsedSourceStore<'a> { - pub fn get_source_text_from_store( - &self, - specifier: &ModuleSpecifier, - ) -> Option> { - let parsed_source = self.0.get_or_parse_source(specifier).ok()??; - Some(Cow::Owned(parsed_source.text_info().clone())) - } -} - -impl SourceTextStore for SourceTextParsedSourceStore<'_> { - fn get_source_text<'a>( - &'a self, - specifier: &ModuleSpecifier, - ) -> Option> { - match self.get_source_text_from_store(specifier) { - Some(text_info) => Some(text_info), - None => { - // todo(#22117): this is extremely hacky and bad because the file - // may have changed by the time we get here. Instead of doing this, - // we should store the text info in the diagnostics - if specifier.scheme() == "file" { - let path = specifier.to_file_path().ok()?; - let text = std::fs::read_to_string(path).ok()?; - Some(Cow::Owned(SourceTextInfo::new(text.into()))) - } else { - None - } - } - } - } -} - -pub enum DiagnosticLevel { - Error, - Warning, -} - -#[derive(Clone, Copy, Debug)] -pub struct DiagnosticSourceRange { - pub start: DiagnosticSourcePos, - pub end: DiagnosticSourcePos, -} - -#[derive(Clone, Copy, Debug)] -pub enum DiagnosticSourcePos { - SourcePos(SourcePos), - ByteIndex(usize), - LineAndCol { - // 0-indexed line number - line: usize, - // 0-indexed column number - column: usize, - }, -} - -impl DiagnosticSourcePos { - fn pos(&self, source: &SourceTextInfo) -> SourcePos { - match self { - DiagnosticSourcePos::SourcePos(pos) => *pos, - DiagnosticSourcePos::ByteIndex(index) => source.range().start() + *index, - DiagnosticSourcePos::LineAndCol { line, column } => { - source.line_start(*line) + *column - } - } - } -} - -#[derive(Clone, Debug)] -pub enum DiagnosticLocation<'a> { - /// The diagnostic is relevant to a specific path. - Path { path: PathBuf }, - /// The diagnostic is relevant to an entire module. - Module { - /// The specifier of the module that contains the diagnostic. - specifier: Cow<'a, ModuleSpecifier>, - }, - /// The diagnostic is relevant to a specific position in a module. - /// - /// This variant will get the relevant `SouceTextInfo` from the cache using - /// the given specifier, and will then calculate the line and column numbers - /// from the given `SourcePos`. - ModulePosition { - /// The specifier of the module that contains the diagnostic. - specifier: Cow<'a, ModuleSpecifier>, - /// The source position of the diagnostic. - source_pos: DiagnosticSourcePos, - }, -} - -impl<'a> DiagnosticLocation<'a> { - /// Return the line and column number of the diagnostic. - /// - /// The line number is 1-indexed. - /// - /// The column number is 1-indexed. This is the number of UTF-16 code units - /// from the start of the line to the diagnostic. - /// Why UTF-16 code units? Because that's what VS Code understands, and - /// everyone uses VS Code. :) - fn position(&self, sources: &dyn SourceTextStore) -> Option<(usize, usize)> { - match self { - DiagnosticLocation::Path { .. } => None, - DiagnosticLocation::Module { .. } => None, - DiagnosticLocation::ModulePosition { - specifier, - source_pos, - } => { - let source = sources.get_source_text(specifier).expect( - "source text should be in the cache if the location is in a file", - ); - let pos = source_pos.pos(&source); - let line_index = source.line_index(pos); - let line_start_pos = source.line_start(line_index); - let content = source.range_text(&SourceRange::new(line_start_pos, pos)); - let line = line_index + 1; - let column = content.encode_utf16().count() + 1; - Some((line, column)) - } - } - } -} - -pub struct DiagnosticSnippet<'a> { - /// The source text for this snippet. The - pub source: DiagnosticSnippetSource<'a>, - /// The piece of the snippet that should be highlighted. - pub highlight: DiagnosticSnippetHighlight<'a>, -} - -pub struct DiagnosticSnippetHighlight<'a> { - /// The range of the snippet that should be highlighted. - pub range: DiagnosticSourceRange, - /// The style of the highlight. - pub style: DiagnosticSnippetHighlightStyle, - /// An optional inline description of the highlight. - pub description: Option>, -} - -pub enum DiagnosticSnippetHighlightStyle { - /// The highlight is an error. This will place red carets under the highlight. - Error, - #[allow(dead_code)] - /// The highlight is a warning. This will place yellow carets under the - /// highlight. - Warning, - #[allow(dead_code)] - /// The highlight shows code additions. This will place green + signs under - /// the highlight and will highlight the code in green. - Addition, - /// The highlight shows a hint. This will place blue dashes under the - /// highlight. - Hint, -} - -impl DiagnosticSnippetHighlightStyle { - fn style_underline( - &self, - s: impl std::fmt::Display, - ) -> impl std::fmt::Display { - match self { - DiagnosticSnippetHighlightStyle::Error => colors::red_bold(s), - DiagnosticSnippetHighlightStyle::Warning => colors::yellow_bold(s), - DiagnosticSnippetHighlightStyle::Addition => colors::green_bold(s), - DiagnosticSnippetHighlightStyle::Hint => colors::intense_blue(s), - } - } - - fn underline_char(&self) -> char { - match self { - DiagnosticSnippetHighlightStyle::Error => '^', - DiagnosticSnippetHighlightStyle::Warning => '^', - DiagnosticSnippetHighlightStyle::Addition => '+', - DiagnosticSnippetHighlightStyle::Hint => '-', - } - } -} - -pub enum DiagnosticSnippetSource<'a> { - /// The specifier of the module that should be displayed in this snippet. The - /// contents of the file will be retrieved from the `SourceTextStore`. - Specifier(Cow<'a, ModuleSpecifier>), - #[allow(dead_code)] - /// The source text that should be displayed in this snippet. - /// - /// This should be used if the text of the snippet is not available in the - /// `SourceTextStore`. - SourceTextInfo(Cow<'a, deno_ast::SourceTextInfo>), -} - -impl<'a> DiagnosticSnippetSource<'a> { - fn to_source_text_info( - &self, - sources: &'a dyn SourceTextStore, - ) -> Cow<'a, SourceTextInfo> { - match self { - DiagnosticSnippetSource::Specifier(specifier) => { - sources.get_source_text(specifier).expect( - "source text should be in the cache if snippet source is a specifier", - ) - } - DiagnosticSnippetSource::SourceTextInfo(info) => info.clone(), - } - } -} - -/// Returns the text of the line with the given number. -fn line_text(source: &SourceTextInfo, line_number: usize) -> &str { - source.line_text(line_number - 1) -} - -/// Returns the text of the line that contains the given position, split at the -/// given position. -fn line_text_split( - source: &SourceTextInfo, - pos: DiagnosticSourcePos, -) -> (&str, &str) { - let pos = pos.pos(source); - let line_index = source.line_index(pos); - let line_start_pos = source.line_start(line_index); - let line_end_pos = source.line_end(line_index); - let before = source.range_text(&SourceRange::new(line_start_pos, pos)); - let after = source.range_text(&SourceRange::new(pos, line_end_pos)); - (before, after) -} - -/// Returns the text of the line that contains the given positions, split at the -/// given positions. -/// -/// If the positions are on different lines, this will panic. -fn line_text_split3( - source: &SourceTextInfo, - start_pos: DiagnosticSourcePos, - end_pos: DiagnosticSourcePos, -) -> (&str, &str, &str) { - let start_pos = start_pos.pos(source); - let end_pos = end_pos.pos(source); - let line_index = source.line_index(start_pos); - assert_eq!( - line_index, - source.line_index(end_pos), - "start and end must be on the same line" - ); - let line_start_pos = source.line_start(line_index); - let line_end_pos = source.line_end(line_index); - let before = source.range_text(&SourceRange::new(line_start_pos, start_pos)); - let between = source.range_text(&SourceRange::new(start_pos, end_pos)); - let after = source.range_text(&SourceRange::new(end_pos, line_end_pos)); - (before, between, after) -} - -/// Returns the line number (1 indexed) of the line that contains the given -/// position. -fn line_number(source: &SourceTextInfo, pos: DiagnosticSourcePos) -> usize { - source.line_index(pos.pos(source)) + 1 -} - -pub trait Diagnostic { - /// The level of the diagnostic. - fn level(&self) -> DiagnosticLevel; - - /// The diagnostic code, like `no-explicit-any` or `ban-untagged-ignore`. - fn code(&self) -> impl fmt::Display + '_; - - /// The human-readable diagnostic message. - fn message(&self) -> impl fmt::Display + '_; - - /// The location this diagnostic is associated with. - fn location(&self) -> DiagnosticLocation; - - /// A snippet showing the source code associated with the diagnostic. - fn snippet(&self) -> Option>; - - /// A hint for fixing the diagnostic. - fn hint(&self) -> Option; - - /// A snippet showing how the diagnostic can be fixed. - fn snippet_fixed(&self) -> Option>; - - fn info(&self) -> Cow<'_, [Cow<'_, str>]>; - - /// An optional URL to the documentation for the diagnostic. - fn docs_url(&self) -> Option; - - fn display<'a>( - &'a self, - sources: &'a dyn SourceTextStore, - ) -> DiagnosticDisplay<'a, Self> { - DiagnosticDisplay { - diagnostic: self, - sources, - } - } -} - -struct RepeatingCharFmt(char, usize); -impl fmt::Display for RepeatingCharFmt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for _ in 0..self.1 { - f.write_char(self.0)?; - } - Ok(()) - } -} - -/// How many spaces a tab should be displayed as. 2 is the default used for -/// `deno fmt`, so we'll use that here. -const TAB_WIDTH: usize = 2; - -struct ReplaceTab<'a>(&'a str); -impl fmt::Display for ReplaceTab<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut written = 0; - for (i, c) in self.0.char_indices() { - if c == '\t' { - self.0[written..i].fmt(f)?; - RepeatingCharFmt(' ', TAB_WIDTH).fmt(f)?; - written = i + 1; - } - } - self.0[written..].fmt(f)?; - Ok(()) - } -} - -/// The width of the string as displayed, assuming tabs are 2 spaces wide. -/// -/// This display width assumes that zero-width-joined characters are the width -/// of their consituent characters. This means that "Person: Red Hair" (which is -/// represented as "Person" + "ZWJ" + "Red Hair") will have a width of 4. -/// -/// Whether this is correct is unfortunately dependent on the font / terminal -/// being used. Here is a list of what terminals consider the length of -/// "Person: Red Hair" to be: -/// -/// | Terminal | Rendered Width | -/// | ---------------- | -------------- | -/// | Windows Terminal | 5 chars | -/// | iTerm (macOS) | 2 chars | -/// | Terminal (macOS) | 2 chars | -/// | VS Code terminal | 4 chars | -/// | GNOME Terminal | 4 chars | -/// -/// If we really wanted to, we could try and detect the terminal being used and -/// adjust the width accordingly. However, this is probably not worth the -/// effort. -fn display_width(str: &str) -> usize { - str.width_cjk() + (str.chars().filter(|c| *c == '\t').count() * TAB_WIDTH) -} - -pub struct DiagnosticDisplay<'a, T: Diagnostic + ?Sized> { - diagnostic: &'a T, - sources: &'a dyn SourceTextStore, -} - -impl Display for DiagnosticDisplay<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - print_diagnostic(f, self.sources, self.diagnostic) - } -} - -// error[missing-return-type]: missing explicit return type on public function -// at /mnt/artemis/Projects/github.com/denoland/deno/test.ts:1:16 -// | -// 1 | export function test() { -// | ^^^^ -// = hint: add an explicit return type to the function -// | -// 1 | export function test(): string { -// | ^^^^^^^^ -// -// info: all functions that are exported from a module must have an explicit return type to support fast check and documentation generation. -// docs: https://jsr.io/d/missing-return-type -fn print_diagnostic( - io: &mut dyn std::fmt::Write, - sources: &dyn SourceTextStore, - diagnostic: &(impl Diagnostic + ?Sized), -) -> Result<(), std::fmt::Error> { - match diagnostic.level() { - DiagnosticLevel::Error => { - write!( - io, - "{}", - colors::red_bold(format_args!("error[{}]", diagnostic.code())) - )?; - } - DiagnosticLevel::Warning => { - write!( - io, - "{}", - colors::yellow_bold(format_args!("warning[{}]", diagnostic.code())) - )?; - } - } - - writeln!(io, ": {}", colors::bold(diagnostic.message()))?; - - let mut max_line_number_digits = 1; - if let Some(snippet) = diagnostic.snippet() { - let source = snippet.source.to_source_text_info(sources); - let last_line = line_number(&source, snippet.highlight.range.end); - max_line_number_digits = max_line_number_digits.max(last_line.ilog10() + 1); - } - if let Some(snippet) = diagnostic.snippet_fixed() { - let source = snippet.source.to_source_text_info(sources); - let last_line = line_number(&source, snippet.highlight.range.end); - max_line_number_digits = max_line_number_digits.max(last_line.ilog10() + 1); - } - - let location = diagnostic.location(); - write!( - io, - "{}{}", - RepeatingCharFmt(' ', max_line_number_digits as usize), - colors::intense_blue("-->"), - )?; - match &location { - DiagnosticLocation::Path { path } => { - write!(io, " {}", colors::cyan(path.display()))?; - } - DiagnosticLocation::Module { specifier } - | DiagnosticLocation::ModulePosition { specifier, .. } => { - if let Ok(path) = specifier.to_file_path() { - write!(io, " {}", colors::cyan(path.display()))?; - } else { - write!(io, " {}", colors::cyan(specifier.as_str()))?; - } - } - } - if let Some((line, column)) = location.position(sources) { - write!( - io, - "{}", - colors::yellow(format_args!(":{}:{}", line, column)) - )?; - } - writeln!(io)?; - - if let Some(snippet) = diagnostic.snippet() { - print_snippet(io, sources, &snippet, max_line_number_digits)?; - }; - - if let Some(hint) = diagnostic.hint() { - write!( - io, - "{} {} ", - RepeatingCharFmt(' ', max_line_number_digits as usize), - colors::intense_blue("=") - )?; - writeln!(io, "{}: {}", colors::bold("hint"), hint)?; - } - - if let Some(snippet) = diagnostic.snippet_fixed() { - print_snippet(io, sources, &snippet, max_line_number_digits)?; - } - - writeln!(io)?; - - let mut needs_final_newline = false; - for info in diagnostic.info().iter() { - needs_final_newline = true; - writeln!(io, " {}: {}", colors::intense_blue("info"), info)?; - } - if let Some(docs_url) = diagnostic.docs_url() { - needs_final_newline = true; - writeln!(io, " {}: {}", colors::intense_blue("docs"), docs_url)?; - } - - if needs_final_newline { - writeln!(io)?; - } - - Ok(()) -} - -/// Prints a snippet to the given writer and returns the line number indent. -fn print_snippet( - io: &mut dyn std::fmt::Write, - sources: &dyn SourceTextStore, - snippet: &DiagnosticSnippet<'_>, - max_line_number_digits: u32, -) -> Result<(), std::fmt::Error> { - let DiagnosticSnippet { source, highlight } = snippet; - - fn print_padded( - io: &mut dyn std::fmt::Write, - text: impl std::fmt::Display, - padding: u32, - ) -> Result<(), std::fmt::Error> { - for _ in 0..padding { - write!(io, " ")?; - } - write!(io, "{}", text)?; - Ok(()) - } - - let source = source.to_source_text_info(sources); - - let start_line_number = line_number(&source, highlight.range.start); - let end_line_number = line_number(&source, highlight.range.end); - - print_padded(io, colors::intense_blue(" | "), max_line_number_digits)?; - writeln!(io)?; - for line_number in start_line_number..=end_line_number { - print_padded( - io, - colors::intense_blue(format_args!("{} | ", line_number)), - max_line_number_digits - line_number.ilog10() - 1, - )?; - - let padding_width; - let highlight_width; - if line_number == start_line_number && start_line_number == end_line_number - { - let (before, between, after) = - line_text_split3(&source, highlight.range.start, highlight.range.end); - write!(io, "{}", ReplaceTab(before))?; - match highlight.style { - DiagnosticSnippetHighlightStyle::Addition => { - write!(io, "{}", colors::green(ReplaceTab(between)))?; - } - _ => { - write!(io, "{}", ReplaceTab(between))?; - } - } - writeln!(io, "{}", ReplaceTab(after))?; - padding_width = display_width(before); - highlight_width = display_width(between); - } else if line_number == start_line_number { - let (before, after) = line_text_split(&source, highlight.range.start); - write!(io, "{}", ReplaceTab(before))?; - match highlight.style { - DiagnosticSnippetHighlightStyle::Addition => { - write!(io, "{}", colors::green(ReplaceTab(after)))?; - } - _ => { - write!(io, "{}", ReplaceTab(after))?; - } - } - writeln!(io)?; - padding_width = display_width(before); - highlight_width = display_width(after); - } else if line_number == end_line_number { - let (before, after) = line_text_split(&source, highlight.range.end); - match highlight.style { - DiagnosticSnippetHighlightStyle::Addition => { - write!(io, "{}", colors::green(ReplaceTab(before)))?; - } - _ => { - write!(io, "{}", ReplaceTab(before))?; - } - } - write!(io, "{}", ReplaceTab(after))?; - writeln!(io)?; - padding_width = 0; - highlight_width = display_width(before); - } else { - let line = line_text(&source, line_number); - writeln!(io, "{}", ReplaceTab(line))?; - padding_width = 0; - highlight_width = display_width(line); - } - - print_padded(io, colors::intense_blue(" | "), max_line_number_digits)?; - write!(io, "{}", RepeatingCharFmt(' ', padding_width))?; - let underline = - RepeatingCharFmt(highlight.style.underline_char(), highlight_width); - write!(io, "{}", highlight.style.style_underline(underline))?; - - if line_number == end_line_number { - if let Some(description) = &highlight.description { - write!(io, " {}", highlight.style.style_underline(description))?; - } - } - - writeln!(io)?; - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use std::borrow::Cow; - - use deno_ast::ModuleSpecifier; - use deno_ast::SourceTextInfo; - - use super::SourceTextStore; - - struct TestSource { - specifier: ModuleSpecifier, - text_info: SourceTextInfo, - } - - impl SourceTextStore for TestSource { - fn get_source_text<'a>( - &'a self, - specifier: &ModuleSpecifier, - ) -> Option> { - if specifier == &self.specifier { - Some(Cow::Borrowed(&self.text_info)) - } else { - None - } - } - } - - #[test] - fn test_display_width() { - assert_eq!(super::display_width("abc"), 3); - assert_eq!(super::display_width("\t"), 2); - assert_eq!(super::display_width("\t\t123"), 7); - assert_eq!(super::display_width("πŸŽ„"), 2); - assert_eq!(super::display_width("πŸŽ„πŸŽ„"), 4); - assert_eq!(super::display_width("πŸ§‘β€πŸ¦°"), 4); - } - - #[test] - fn test_position_in_file_from_text_info_simple() { - let specifier: ModuleSpecifier = "file:///dev/test.ts".parse().unwrap(); - let text_info = SourceTextInfo::new("foo\nbar\nbaz".into()); - let pos = text_info.line_start(1); - let sources = TestSource { - specifier: specifier.clone(), - text_info, - }; - let location = super::DiagnosticLocation::ModulePosition { - specifier: Cow::Borrowed(&specifier), - source_pos: super::DiagnosticSourcePos::SourcePos(pos), - }; - let position = location.position(&sources).unwrap(); - assert_eq!(position, (2, 1)) - } - - #[test] - fn test_position_in_file_from_text_info_emoji() { - let specifier: ModuleSpecifier = "file:///dev/test.ts".parse().unwrap(); - let text_info = SourceTextInfo::new("πŸ§‘β€πŸ¦°text".into()); - let pos = text_info.line_start(0) + 11; // the end of the emoji - let sources = TestSource { - specifier: specifier.clone(), - text_info, - }; - let location = super::DiagnosticLocation::ModulePosition { - specifier: Cow::Borrowed(&specifier), - source_pos: super::DiagnosticSourcePos::SourcePos(pos), - }; - let position = location.position(&sources).unwrap(); - assert_eq!(position, (1, 6)) - } -} diff --git a/cli/errors.rs b/cli/errors.rs index c2539df7ee..fce286f159 100644 --- a/cli/errors.rs +++ b/cli/errors.rs @@ -9,7 +9,7 @@ //! Diagnostics are compile-time type errors, whereas JsErrors are runtime //! exceptions. -use deno_ast::Diagnostic; +use deno_ast::ParseDiagnostic; use deno_core::error::AnyError; use deno_graph::source::ResolveError; use deno_graph::ModuleError; @@ -22,7 +22,7 @@ fn get_import_map_error_class(_: &ImportMapError) -> &'static str { "URIError" } -fn get_diagnostic_class(_: &Diagnostic) -> &'static str { +fn get_diagnostic_class(_: &ParseDiagnostic) -> &'static str { "SyntaxError" } @@ -67,7 +67,10 @@ pub fn get_error_class_name(e: &AnyError) -> &'static str { e.downcast_ref::() .map(get_import_map_error_class) }) - .or_else(|| e.downcast_ref::().map(get_diagnostic_class)) + .or_else(|| { + e.downcast_ref::() + .map(get_diagnostic_class) + }) .or_else(|| { e.downcast_ref::() .map(get_module_graph_error_class) diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 6c6d7cab4a..96ee422c6a 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -20,6 +20,7 @@ use deno_core::serde::Deserialize; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::ModuleSpecifier; +use deno_lint::diagnostic::LintDiagnostic; use deno_lint::rules::LintRule; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::NpmResolver; @@ -118,15 +119,21 @@ impl Reference { } } -fn as_lsp_range(range: &deno_lint::diagnostic::Range) -> Range { +fn as_lsp_range(diagnostic: &LintDiagnostic) -> Range { + let start_lc = diagnostic + .text_info + .line_and_column_index(diagnostic.range.start); + let end_lc = diagnostic + .text_info + .line_and_column_index(diagnostic.range.end); Range { start: Position { - line: range.start.line_index as u32, - character: range.start.column_index as u32, + line: start_lc.line_index as u32, + character: start_lc.column_index as u32, }, end: Position { - line: range.end.line_index as u32, - character: range.end.column_index as u32, + line: end_lc.line_index as u32, + character: end_lc.column_index as u32, }, } } @@ -142,12 +149,12 @@ pub fn get_lint_references( lint_diagnostics .into_iter() .map(|d| Reference { + range: as_lsp_range(&d), category: Category::Lint { message: d.message, code: d.code, hint: d.hint, }, - range: as_lsp_range(&d.range), }) .collect(), ) @@ -1060,36 +1067,6 @@ mod tests { } } - #[test] - fn test_as_lsp_range() { - let fixture = deno_lint::diagnostic::Range { - start: deno_lint::diagnostic::Position { - line_index: 0, - column_index: 2, - byte_index: 23, - }, - end: deno_lint::diagnostic::Position { - line_index: 1, - column_index: 0, - byte_index: 33, - }, - }; - let actual = as_lsp_range(&fixture); - assert_eq!( - actual, - lsp::Range { - start: lsp::Position { - line: 0, - character: 2, - }, - end: lsp::Position { - line: 1, - character: 0, - }, - } - ); - } - #[test] fn test_try_reverse_map_package_json_exports() { let exports = json!({ diff --git a/cli/lsp/code_lens.rs b/cli/lsp/code_lens.rs index adf1d5c63d..59787dd84e 100644 --- a/cli/lsp/code_lens.rs +++ b/cli/lsp/code_lens.rs @@ -560,7 +560,7 @@ mod tests { Deno.test(`test template literal name`, () => {}); "#; let parsed_module = deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.to_string(), + specifier: specifier.clone(), text_info: SourceTextInfo::new(source.into()), media_type: MediaType::TypeScript, capture_tokens: true, diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index c758d341bc..94d0e979b1 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -246,7 +246,7 @@ impl AssetOrDocument { pub fn maybe_parsed_source( &self, - ) -> Option> { + ) -> Option> { self.document().and_then(|d| d.maybe_parsed_source()) } @@ -283,7 +283,7 @@ impl DocumentDependencies { } type ModuleResult = Result; -type ParsedSourceResult = Result; +type ParsedSourceResult = Result; #[derive(Debug)] struct DocumentInner { @@ -595,7 +595,7 @@ impl Document { pub fn maybe_parsed_source( &self, - ) -> Option> { + ) -> Option> { self.0.maybe_parsed_source.clone() } @@ -1855,7 +1855,7 @@ fn parse_source( maybe_headers: Option<&HashMap>, ) -> ParsedSourceResult { deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.to_string(), + specifier: specifier.clone(), text_info, media_type: MediaType::from_specifier_and_headers(specifier, maybe_headers), capture_tokens: true, diff --git a/cli/lsp/testing/collectors.rs b/cli/lsp/testing/collectors.rs index a66e56948b..8579ccc7d6 100644 --- a/cli/lsp/testing/collectors.rs +++ b/cli/lsp/testing/collectors.rs @@ -644,7 +644,7 @@ pub mod tests { let specifier = resolve_url("file:///a/example.ts").unwrap(); let parsed_module = deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.to_string(), + specifier: specifier.clone(), text_info: deno_ast::SourceTextInfo::new(source.into()), media_type: deno_ast::MediaType::TypeScript, capture_tokens: true, diff --git a/cli/main.rs b/cli/main.rs index 9d0ade0858..5e446efb8f 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -5,7 +5,6 @@ mod auth_tokens; mod cache; mod cdp; mod deno_std; -mod diagnostics; mod emit; mod errors; mod factory; diff --git a/cli/node.rs b/cli/node.rs index a66713685b..cbe0aaaf1c 100644 --- a/cli/node.rs +++ b/cli/node.rs @@ -67,7 +67,7 @@ impl CliCjsCodeAnalyzer { } let parsed_source = deno_ast::parse_script(deno_ast::ParseParams { - specifier: specifier.to_string(), + specifier: specifier.clone(), text_info: deno_ast::SourceTextInfo::new(source.into()), media_type, capture_tokens: true, diff --git a/cli/tests/testdata/lint/expected_from_stdin_json.out b/cli/tests/testdata/lint/expected_from_stdin_json.out index 26bf7ddc70..9e1188bcdb 100644 --- a/cli/tests/testdata/lint/expected_from_stdin_json.out +++ b/cli/tests/testdata/lint/expected_from_stdin_json.out @@ -1,6 +1,7 @@ { "diagnostics": [ { + "filename": "[WILDCARD]$deno$stdin.ts", "range": { "start": { "line": 1, @@ -13,7 +14,6 @@ "bytePos": 11 } }, - "filename": "[WILDCARD]$deno$stdin.ts", "message": "`any` type is not allowed", "code": "no-explicit-any", "hint": [WILDCARD] diff --git a/cli/tests/testdata/lint/expected_json.out b/cli/tests/testdata/lint/expected_json.out index 08ea0d3e05..95c3d30ba4 100644 --- a/cli/tests/testdata/lint/expected_json.out +++ b/cli/tests/testdata/lint/expected_json.out @@ -1,6 +1,7 @@ { "diagnostics": [ { + "filename": "[WILDCARD]file1.js", "range": { "start": { "line": 1, @@ -13,12 +14,12 @@ "bytePos": 19 } }, - "filename": "[WILDCARD]file1.js", "message": "Ignore directive requires lint rule name(s)", "code": "ban-untagged-ignore", "hint": [WILDCARD] }, { + "filename": "[WILDCARD]file1.js", "range": { "start": { "line": 2, @@ -31,12 +32,12 @@ "bytePos": 36 } }, - "filename": "[WILDCARD]file1.js", "message": "Empty block statement", "code": "no-empty", "hint": [WILDCARD] }, { + "filename": "[WILDCARD]file2.ts", "range": { "start": { "line": 3, @@ -49,7 +50,6 @@ "bytePos": 59 } }, - "filename": "[WILDCARD]file2.ts", "message": "Empty block statement", "code": "no-empty", "hint": [WILDCARD] diff --git a/cli/tests/testdata/lint/with_report_config_override.out b/cli/tests/testdata/lint/with_report_config_override.out index ac633d911d..7ca7481583 100644 --- a/cli/tests/testdata/lint/with_report_config_override.out +++ b/cli/tests/testdata/lint/with_report_config_override.out @@ -1,6 +1,7 @@ { "diagnostics": [ { + "filename": "[WILDCARD]a.ts", "range": { "start": { "line": 1, @@ -13,12 +14,12 @@ "bytePos": 12 } }, - "filename": "[WILDCARD]a.ts", "message": "TODO should be tagged with (@username) or (#issue)", "code": "ban-untagged-todo", "hint": "Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)" }, { + "filename": "[WILDCARD]a.ts", "range": { "start": { "line": 2, @@ -31,7 +32,6 @@ "bytePos": 25 } }, - "filename": "[WILDCARD]a.ts", "message": "`add` is never used", "code": "no-unused-vars", "hint": "If this is intentional, prefix it with an underscore like `_add`" diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs index 729ee05fcb..d2cd0c2a22 100644 --- a/cli/tools/doc.rs +++ b/cli/tools/doc.rs @@ -4,24 +4,14 @@ use crate::args::DocFlags; use crate::args::DocHtmlFlag; use crate::args::DocSourceFileFlag; use crate::args::Flags; -use crate::cache::LazyGraphSourceParser; use crate::colors; -use crate::diagnostics::Diagnostic; -use crate::diagnostics::DiagnosticLevel; -use crate::diagnostics::DiagnosticLocation; -use crate::diagnostics::DiagnosticSnippet; -use crate::diagnostics::DiagnosticSnippetHighlight; -use crate::diagnostics::DiagnosticSnippetHighlightStyle; -use crate::diagnostics::DiagnosticSnippetSource; -use crate::diagnostics::DiagnosticSourcePos; -use crate::diagnostics::DiagnosticSourceRange; -use crate::diagnostics::SourceTextParsedSourceStore; use crate::display::write_json_to_stdout; use crate::display::write_to_stdout_ignore_sigpipe; use crate::factory::CliFactory; use crate::graph_util::graph_lock_or_exit; use crate::tsc::get_types_declaration_file_text; use crate::util::fs::collect_specifiers; +use deno_ast::diagnostics::Diagnostic; use deno_config::glob::FilePatterns; use deno_config::glob::PathOrPatternSet; use deno_core::anyhow::bail; @@ -34,10 +24,7 @@ use deno_graph::ModuleAnalyzer; use deno_graph::ModuleParser; use deno_graph::ModuleSpecifier; use doc::DocDiagnostic; -use doc::DocDiagnosticKind; use indexmap::IndexMap; -use lsp_types::Url; -use std::borrow::Cow; use std::collections::BTreeMap; use std::rc::Rc; @@ -143,10 +130,7 @@ pub async fn doc(flags: Flags, doc_flags: DocFlags) -> Result<(), AnyError> { if doc_flags.lint { let diagnostics = doc_parser.take_diagnostics(); - check_diagnostics( - LazyGraphSourceParser::new(parsed_source_cache, &graph), - &diagnostics, - )?; + check_diagnostics(&diagnostics)?; } doc_nodes_by_url @@ -252,6 +236,7 @@ async fn generate_docs_directory( hide_module_doc_title: false, href_resolver: Rc::new(DocResolver { deno_ns }), sidebar_flatten_namespaces: false, + usage_composer: None, }; let files = deno_doc::html::generate(options, doc_nodes_by_url) @@ -308,118 +293,7 @@ fn print_docs_to_stdout( write_to_stdout_ignore_sigpipe(details.as_bytes()).map_err(AnyError::from) } -impl Diagnostic for DocDiagnostic { - fn level(&self) -> DiagnosticLevel { - DiagnosticLevel::Error - } - - fn code(&self) -> impl std::fmt::Display + '_ { - match self.kind { - DocDiagnosticKind::MissingJsDoc => "missing-jsdoc", - DocDiagnosticKind::MissingExplicitType => "missing-explicit-type", - DocDiagnosticKind::MissingReturnType => "missing-return-type", - DocDiagnosticKind::PrivateTypeRef { .. } => "private-type-ref", - } - } - - fn message(&self) -> impl std::fmt::Display + '_ { - match &self.kind { - DocDiagnosticKind::MissingJsDoc => { - Cow::Borrowed("exported symbol is missing JSDoc documentation") - } - DocDiagnosticKind::MissingExplicitType => { - Cow::Borrowed("exported symbol is missing an explicit type annotation") - } - DocDiagnosticKind::MissingReturnType => Cow::Borrowed( - "exported function is missing an explicit return type annotation", - ), - DocDiagnosticKind::PrivateTypeRef { - reference, name, .. - } => Cow::Owned(format!( - "public type '{name}' references private type '{reference}'", - )), - } - } - - fn location(&self) -> DiagnosticLocation { - let specifier = Url::parse(&self.location.filename).unwrap(); - DiagnosticLocation::ModulePosition { - specifier: Cow::Owned(specifier), - source_pos: DiagnosticSourcePos::ByteIndex(self.location.byte_index), - } - } - - fn snippet(&self) -> Option> { - let specifier = Url::parse(&self.location.filename).unwrap(); - Some(DiagnosticSnippet { - source: DiagnosticSnippetSource::Specifier(Cow::Owned(specifier)), - highlight: DiagnosticSnippetHighlight { - style: DiagnosticSnippetHighlightStyle::Error, - range: DiagnosticSourceRange { - start: DiagnosticSourcePos::ByteIndex(self.location.byte_index), - end: DiagnosticSourcePos::ByteIndex(self.location.byte_index + 1), - }, - description: None, - }, - }) - } - - fn hint(&self) -> Option { - match &self.kind { - DocDiagnosticKind::PrivateTypeRef { .. } => { - Some("make the referenced type public or remove the reference") - } - _ => None, - } - } - fn snippet_fixed(&self) -> Option> { - match &self.kind { - DocDiagnosticKind::PrivateTypeRef { - reference_location, .. - } => { - let specifier = Url::parse(&reference_location.filename).unwrap(); - Some(DiagnosticSnippet { - source: DiagnosticSnippetSource::Specifier(Cow::Owned(specifier)), - highlight: DiagnosticSnippetHighlight { - style: DiagnosticSnippetHighlightStyle::Hint, - range: DiagnosticSourceRange { - start: DiagnosticSourcePos::ByteIndex( - reference_location.byte_index, - ), - end: DiagnosticSourcePos::ByteIndex( - reference_location.byte_index + 1, - ), - }, - description: Some(Cow::Borrowed("this is the referenced type")), - }, - }) - } - _ => None, - } - } - - fn info(&self) -> std::borrow::Cow<'_, [std::borrow::Cow<'_, str>]> { - match &self.kind { - DocDiagnosticKind::MissingJsDoc => Cow::Borrowed(&[]), - DocDiagnosticKind::MissingExplicitType => Cow::Borrowed(&[]), - DocDiagnosticKind::MissingReturnType => Cow::Borrowed(&[]), - DocDiagnosticKind::PrivateTypeRef { .. } => { - Cow::Borrowed(&[Cow::Borrowed( - "to ensure documentation is complete all types that are exposed in the public API must be public", - )]) - } - } - } - - fn docs_url(&self) -> Option { - None::<&str> - } -} - -fn check_diagnostics( - source_parser: LazyGraphSourceParser, - diagnostics: &[DocDiagnostic], -) -> Result<(), AnyError> { +fn check_diagnostics(diagnostics: &[DocDiagnostic]) -> Result<(), AnyError> { if diagnostics.is_empty() { return Ok(()); } @@ -441,8 +315,7 @@ fn check_diagnostics( for (_, diagnostics_by_col) in diagnostics_by_lc { for (_, diagnostics) in diagnostics_by_col { for diagnostic in diagnostics { - let sources = SourceTextParsedSourceStore(source_parser); - log::error!("{}", diagnostic.display(&sources)); + log::error!("{}", diagnostic.display()); } } } diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index e9f84fd77d..32b47e453c 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -8,33 +8,22 @@ use crate::args::LintOptions; use crate::args::LintReporterKind; use crate::args::LintRulesConfig; use crate::colors; -use crate::diagnostics::Diagnostic; -use crate::diagnostics::DiagnosticLevel; -use crate::diagnostics::DiagnosticLocation; -use crate::diagnostics::DiagnosticSnippet; -use crate::diagnostics::DiagnosticSnippetHighlight; -use crate::diagnostics::DiagnosticSnippetHighlightStyle; -use crate::diagnostics::DiagnosticSnippetSource; -use crate::diagnostics::DiagnosticSourcePos; -use crate::diagnostics::DiagnosticSourceRange; -use crate::diagnostics::SourceTextStore; use crate::factory::CliFactory; use crate::tools::fmt::run_parallelized; use crate::util::file_watcher; use crate::util::fs::canonicalize_path; +use crate::util::fs::specifier_from_file_path; use crate::util::fs::FileCollector; use crate::util::path::is_script_ext; use crate::util::sync::AtomicFlag; +use deno_ast::diagnostics::Diagnostic; use deno_ast::MediaType; -use deno_ast::ModuleSpecifier; use deno_ast::ParsedSource; -use deno_ast::SourceTextInfo; use deno_config::glob::FilePatterns; use deno_core::anyhow::bail; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::serde_json; -use deno_core::url; use deno_lint::diagnostic::LintDiagnostic; use deno_lint::linter::LintFileOptions; use deno_lint::linter::Linter; @@ -44,7 +33,6 @@ use deno_lint::rules::LintRule; use log::debug; use log::info; use serde::Serialize; -use std::borrow::Cow; use std::fs; use std::io::stdin; use std::io::Read; @@ -124,9 +112,12 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { let reporter_lock = Arc::new(Mutex::new(create_reporter(reporter_kind))); let lint_rules = get_config_rules_err_empty(lint_options.rules)?; let file_path = cli_options.initial_cwd().join(STDIN_FILE_NAME); - let file_path = file_path.to_string_lossy(); let r = lint_stdin(&file_path, lint_rules); - let success = handle_lint_result(&file_path, r, reporter_lock.clone()); + let success = handle_lint_result( + &file_path.to_string_lossy(), + r, + reporter_lock.clone(), + ); reporter_lock.lock().unwrap().close(1); success } else { @@ -278,13 +269,13 @@ fn lint_file( source_code: String, lint_rules: Vec<&'static dyn LintRule>, ) -> Result<(Vec, ParsedSource), AnyError> { - let filename = file_path.to_string_lossy().to_string(); - let media_type = MediaType::from_path(file_path); + let specifier = specifier_from_file_path(file_path)?; + let media_type = MediaType::from_specifier(&specifier); let linter = create_linter(lint_rules); let (source, file_diagnostics) = linter.lint_file(LintFileOptions { - filename, + specifier, media_type, source_code: source_code.clone(), })?; @@ -296,7 +287,7 @@ fn lint_file( /// Treats input as TypeScript. /// Compatible with `--json` flag. fn lint_stdin( - file_path: &str, + file_path: &Path, lint_rules: Vec<&'static dyn LintRule>, ) -> Result<(Vec, ParsedSource), AnyError> { let mut source_code = String::new(); @@ -307,7 +298,7 @@ fn lint_stdin( let linter = create_linter(lint_rules); let (source, file_diagnostics) = linter.lint_file(LintFileOptions { - filename: file_path.to_string(), + specifier: specifier_from_file_path(file_path)?, source_code: source_code.clone(), media_type: MediaType::TypeScript, })?; @@ -324,7 +315,10 @@ fn handle_lint_result( match result { Ok((mut file_diagnostics, source)) => { - sort_diagnostics(&mut file_diagnostics); + file_diagnostics.sort_by(|a, b| match a.specifier.cmp(&b.specifier) { + std::cmp::Ordering::Equal => a.range.start.cmp(&b.range.start), + file_order => file_order, + }); for d in file_diagnostics.iter() { reporter.visit_diagnostic(d, &source); } @@ -359,77 +353,11 @@ impl PrettyLintReporter { } } -impl Diagnostic for LintDiagnostic { - fn level(&self) -> DiagnosticLevel { - DiagnosticLevel::Error - } - - fn code(&self) -> impl std::fmt::Display + '_ { - &self.code - } - - fn message(&self) -> impl std::fmt::Display + '_ { - &self.message - } - - fn location(&self) -> DiagnosticLocation { - let specifier = url::Url::from_file_path(&self.filename).unwrap(); - DiagnosticLocation::ModulePosition { - specifier: Cow::Owned(specifier), - source_pos: DiagnosticSourcePos::ByteIndex(self.range.start.byte_index), - } - } - - fn snippet(&self) -> Option> { - let specifier = url::Url::from_file_path(&self.filename).unwrap(); - let range = DiagnosticSourceRange { - start: DiagnosticSourcePos::ByteIndex(self.range.start.byte_index), - end: DiagnosticSourcePos::ByteIndex(self.range.end.byte_index), - }; - Some(DiagnosticSnippet { - source: DiagnosticSnippetSource::Specifier(Cow::Owned(specifier)), - highlight: DiagnosticSnippetHighlight { - range, - style: DiagnosticSnippetHighlightStyle::Error, - description: None, - }, - }) - } - - fn hint(&self) -> Option { - self.hint.as_ref().map(|h| h as &dyn std::fmt::Display) - } - - fn snippet_fixed(&self) -> Option> { - None // todo - } - - fn info(&self) -> Cow<'_, [std::borrow::Cow<'_, str>]> { - Cow::Borrowed(&[]) - } - - fn docs_url(&self) -> Option { - Some(format!("https://lint.deno.land/#{}", &self.code)) - } -} - -struct OneSource<'a>(&'a ParsedSource); - -impl SourceTextStore for OneSource<'_> { - fn get_source_text<'a>( - &'a self, - _specifier: &ModuleSpecifier, - ) -> Option> { - Some(Cow::Borrowed(self.0.text_info())) - } -} - impl LintReporter for PrettyLintReporter { - fn visit_diagnostic(&mut self, d: &LintDiagnostic, source: &ParsedSource) { + fn visit_diagnostic(&mut self, d: &LintDiagnostic, _source: &ParsedSource) { self.lint_count += 1; - let sources = OneSource(source); - eprintln!("{}", d.display(&sources)); + eprintln!("{}", d.display()); } fn visit_error(&mut self, file_path: &str, err: &AnyError) { @@ -466,11 +394,12 @@ impl LintReporter for CompactLintReporter { fn visit_diagnostic(&mut self, d: &LintDiagnostic, _source: &ParsedSource) { self.lint_count += 1; + let line_and_column = d.text_info.line_and_column_display(d.range.start); eprintln!( "{}: line {}, col {} - {} ({})", - d.filename, - d.range.start.line_index + 1, - d.range.start.column_index + 1, + d.specifier, + line_and_column.line_number, + line_and_column.column_number, d.message, d.code ) @@ -496,9 +425,47 @@ impl LintReporter for CompactLintReporter { } } +// WARNING: Ensure doesn't change because it's used in the JSON output +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JsonDiagnosticLintPosition { + /// The 1-indexed line number. + pub line: usize, + /// The 0-indexed column index. + pub col: usize, + pub byte_pos: usize, +} + +impl JsonDiagnosticLintPosition { + pub fn new(byte_index: usize, loc: deno_ast::LineAndColumnIndex) -> Self { + JsonDiagnosticLintPosition { + line: loc.line_index + 1, + col: loc.column_index, + byte_pos: byte_index, + } + } +} + +// WARNING: Ensure doesn't change because it's used in the JSON output +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +struct JsonLintDiagnosticRange { + pub start: JsonDiagnosticLintPosition, + pub end: JsonDiagnosticLintPosition, +} + +// WARNING: Ensure doesn't change because it's used in the JSON output +#[derive(Clone, Serialize)] +struct JsonLintDiagnostic { + pub filename: String, + pub range: JsonLintDiagnosticRange, + pub message: String, + pub code: String, + pub hint: Option, +} + #[derive(Serialize)] struct JsonLintReporter { - diagnostics: Vec, + diagnostics: Vec, errors: Vec, } @@ -513,7 +480,22 @@ impl JsonLintReporter { impl LintReporter for JsonLintReporter { fn visit_diagnostic(&mut self, d: &LintDiagnostic, _source: &ParsedSource) { - self.diagnostics.push(d.clone()); + self.diagnostics.push(JsonLintDiagnostic { + filename: d.specifier.to_string(), + range: JsonLintDiagnosticRange { + start: JsonDiagnosticLintPosition::new( + d.range.start.as_byte_index(d.text_info.range().start), + d.text_info.line_and_column_index(d.range.start), + ), + end: JsonDiagnosticLintPosition::new( + d.range.end.as_byte_index(d.text_info.range().start), + d.text_info.line_and_column_index(d.range.end), + ), + }, + message: d.message.clone(), + code: d.code.clone(), + hint: d.hint.clone(), + }); } fn visit_error(&mut self, file_path: &str, err: &AnyError) { @@ -530,19 +512,16 @@ impl LintReporter for JsonLintReporter { } } -fn sort_diagnostics(diagnostics: &mut [LintDiagnostic]) { +fn sort_diagnostics(diagnostics: &mut [JsonLintDiagnostic]) { // Sort so that we guarantee a deterministic output which is useful for tests diagnostics.sort_by(|a, b| { use std::cmp::Ordering; let file_order = a.filename.cmp(&b.filename); match file_order { Ordering::Equal => { - let line_order = - a.range.start.line_index.cmp(&b.range.start.line_index); + let line_order = a.range.start.line.cmp(&b.range.start.line); match line_order { - Ordering::Equal => { - a.range.start.column_index.cmp(&b.range.start.column_index) - } + Ordering::Equal => a.range.start.col.cmp(&b.range.start.col), _ => line_order, } } diff --git a/cli/tools/registry/diagnostics.rs b/cli/tools/registry/diagnostics.rs index e7f9473038..aeb5d61e2a 100644 --- a/cli/tools/registry/diagnostics.rs +++ b/cli/tools/registry/diagnostics.rs @@ -1,28 +1,25 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::borrow::Cow; -use std::fmt::Display; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; +use deno_ast::diagnostics::Diagnostic; +use deno_ast::diagnostics::DiagnosticLevel; +use deno_ast::diagnostics::DiagnosticLocation; +use deno_ast::diagnostics::DiagnosticSnippet; +use deno_ast::diagnostics::DiagnosticSnippetHighlight; +use deno_ast::diagnostics::DiagnosticSnippetHighlightStyle; +use deno_ast::diagnostics::DiagnosticSourcePos; +use deno_ast::diagnostics::DiagnosticSourceRange; use deno_ast::swc::common::util::take::Take; +use deno_ast::SourceTextInfo; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_graph::FastCheckDiagnostic; use lsp_types::Url; -use crate::cache::LazyGraphSourceParser; -use crate::diagnostics::Diagnostic; -use crate::diagnostics::DiagnosticLevel; -use crate::diagnostics::DiagnosticLocation; -use crate::diagnostics::DiagnosticSnippet; -use crate::diagnostics::DiagnosticSnippetHighlight; -use crate::diagnostics::DiagnosticSnippetHighlightStyle; -use crate::diagnostics::DiagnosticSnippetSource; -use crate::diagnostics::DiagnosticSourcePos; -use crate::diagnostics::DiagnosticSourceRange; -use crate::diagnostics::SourceTextParsedSourceStore; use crate::util::import_map::ImportMapUnfurlDiagnostic; #[derive(Clone, Default)] @@ -31,16 +28,12 @@ pub struct PublishDiagnosticsCollector { } impl PublishDiagnosticsCollector { - pub fn print_and_error( - &self, - sources: LazyGraphSourceParser, - ) -> Result<(), AnyError> { + pub fn print_and_error(&self) -> Result<(), AnyError> { let mut errors = 0; let mut has_zap_errors = false; let diagnostics = self.diagnostics.lock().unwrap().take(); - let sources = SourceTextParsedSourceStore(sources); for diagnostic in diagnostics { - eprint!("{}", diagnostic.display(&sources)); + eprint!("{}", diagnostic.display()); if matches!(diagnostic.level(), DiagnosticLevel::Error) { errors += 1; } @@ -90,6 +83,7 @@ pub enum PublishDiagnostic { InvalidExternalImport { kind: String, imported: Url, + text_info: SourceTextInfo, referrer: deno_graph::Range, }, } @@ -110,22 +104,22 @@ impl Diagnostic for PublishDiagnostic { } } - fn code(&self) -> impl Display + '_ { + fn code(&self) -> Cow<'_, str> { use PublishDiagnostic::*; match &self { FastCheck(diagnostic) => diagnostic.code(), - ImportMapUnfurl(diagnostic) => diagnostic.code(), - InvalidPath { .. } => "invalid-path", - DuplicatePath { .. } => "case-insensitive-duplicate-path", - UnsupportedFileType { .. } => "unsupported-file-type", - InvalidExternalImport { .. } => "invalid-external-import", + ImportMapUnfurl(diagnostic) => Cow::Borrowed(diagnostic.code()), + InvalidPath { .. } => Cow::Borrowed("invalid-path"), + DuplicatePath { .. } => Cow::Borrowed("case-insensitive-duplicate-path"), + UnsupportedFileType { .. } => Cow::Borrowed("unsupported-file-type"), + InvalidExternalImport { .. } => Cow::Borrowed("invalid-external-import"), } } - fn message(&self) -> impl Display + '_ { + fn message(&self) -> Cow<'_, str> { use PublishDiagnostic::*; match &self { - FastCheck(diagnostic) => Cow::Owned(diagnostic.to_string()) , + FastCheck(diagnostic) => diagnostic.message(), ImportMapUnfurl(diagnostic) => Cow::Borrowed(diagnostic.message()), InvalidPath { message, .. } => Cow::Borrowed(message.as_str()), DuplicatePath { .. } => { @@ -141,21 +135,15 @@ impl Diagnostic for PublishDiagnostic { fn location(&self) -> DiagnosticLocation { use PublishDiagnostic::*; match &self { - FastCheck(diagnostic) => match diagnostic.range() { - Some(range) => DiagnosticLocation::ModulePosition { - specifier: Cow::Borrowed(diagnostic.specifier()), - source_pos: DiagnosticSourcePos::SourcePos(range.range.start), - }, - None => DiagnosticLocation::Module { - specifier: Cow::Borrowed(diagnostic.specifier()), - }, - }, + FastCheck(diagnostic) => diagnostic.location(), ImportMapUnfurl(diagnostic) => match diagnostic { ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { specifier, + text_info, range, } => DiagnosticLocation::ModulePosition { specifier: Cow::Borrowed(specifier), + text_info: Cow::Borrowed(text_info), source_pos: DiagnosticSourcePos::SourcePos(range.start), }, }, @@ -168,41 +156,31 @@ impl Diagnostic for PublishDiagnostic { UnsupportedFileType { specifier, .. } => DiagnosticLocation::Module { specifier: Cow::Borrowed(specifier), }, - InvalidExternalImport { referrer, .. } => { - DiagnosticLocation::ModulePosition { - specifier: Cow::Borrowed(&referrer.specifier), - source_pos: DiagnosticSourcePos::LineAndCol { - line: referrer.start.line, - column: referrer.start.character, - }, - } - } + InvalidExternalImport { + referrer, + text_info, + .. + } => DiagnosticLocation::ModulePosition { + specifier: Cow::Borrowed(&referrer.specifier), + text_info: Cow::Borrowed(text_info), + source_pos: DiagnosticSourcePos::LineAndCol { + line: referrer.start.line, + column: referrer.start.character, + }, + }, } } fn snippet(&self) -> Option> { match &self { - PublishDiagnostic::FastCheck(diagnostic) => { - diagnostic.range().map(|range| DiagnosticSnippet { - source: DiagnosticSnippetSource::Specifier(Cow::Borrowed( - diagnostic.specifier(), - )), - highlight: DiagnosticSnippetHighlight { - style: DiagnosticSnippetHighlightStyle::Error, - range: DiagnosticSourceRange { - start: DiagnosticSourcePos::SourcePos(range.range.start), - end: DiagnosticSourcePos::SourcePos(range.range.end), - }, - description: diagnostic.range_description().map(Cow::Borrowed), - }, - }) - } + PublishDiagnostic::FastCheck(diagnostic) => diagnostic.snippet(), PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { - specifier, + text_info, range, + .. } => Some(DiagnosticSnippet { - source: DiagnosticSnippetSource::Specifier(Cow::Borrowed(specifier)), + source: Cow::Borrowed(text_info), highlight: DiagnosticSnippetHighlight { style: DiagnosticSnippetHighlightStyle::Warning, range: DiagnosticSourceRange { @@ -216,44 +194,44 @@ impl Diagnostic for PublishDiagnostic { PublishDiagnostic::InvalidPath { .. } => None, PublishDiagnostic::DuplicatePath { .. } => None, PublishDiagnostic::UnsupportedFileType { .. } => None, - PublishDiagnostic::InvalidExternalImport { referrer, .. } => { - Some(DiagnosticSnippet { - source: DiagnosticSnippetSource::Specifier(Cow::Borrowed( - &referrer.specifier, - )), - highlight: DiagnosticSnippetHighlight { - style: DiagnosticSnippetHighlightStyle::Error, - range: DiagnosticSourceRange { - start: DiagnosticSourcePos::LineAndCol { - line: referrer.start.line, - column: referrer.start.character, - }, - end: DiagnosticSourcePos::LineAndCol { - line: referrer.end.line, - column: referrer.end.character, - }, + PublishDiagnostic::InvalidExternalImport { + referrer, + text_info, + .. + } => Some(DiagnosticSnippet { + source: Cow::Borrowed(text_info), + highlight: DiagnosticSnippetHighlight { + style: DiagnosticSnippetHighlightStyle::Error, + range: DiagnosticSourceRange { + start: DiagnosticSourcePos::LineAndCol { + line: referrer.start.line, + column: referrer.start.character, + }, + end: DiagnosticSourcePos::LineAndCol { + line: referrer.end.line, + column: referrer.end.character, }, - description: Some("the specifier".into()), }, - }) - } + description: Some("the specifier".into()), + }, + }), } } - fn hint(&self) -> Option { + fn hint(&self) -> Option> { match &self { - PublishDiagnostic::FastCheck(diagnostic) => Some(diagnostic.fix_hint()), + PublishDiagnostic::FastCheck(diagnostic) => diagnostic.hint(), PublishDiagnostic::ImportMapUnfurl(_) => None, PublishDiagnostic::InvalidPath { .. } => Some( - "rename or remove the file, or add it to 'publish.exclude' in the config file", + Cow::Borrowed("rename or remove the file, or add it to 'publish.exclude' in the config file"), ), PublishDiagnostic::DuplicatePath { .. } => Some( - "rename or remove the file", + Cow::Borrowed("rename or remove the file"), ), PublishDiagnostic::UnsupportedFileType { .. } => Some( - "remove the file, or add it to 'publish.exclude' in the config file", + Cow::Borrowed("remove the file, or add it to 'publish.exclude' in the config file"), ), - PublishDiagnostic::InvalidExternalImport { .. } => Some("replace this import with one from jsr or npm, or vendor the dependency into your package") + PublishDiagnostic::InvalidExternalImport { .. } => Some(Cow::Borrowed("replace this import with one from jsr or npm, or vendor the dependency into your package")) } } @@ -264,12 +242,7 @@ impl Diagnostic for PublishDiagnostic { fn info(&self) -> Cow<'_, [Cow<'_, str>]> { match &self { PublishDiagnostic::FastCheck(diagnostic) => { - let infos = diagnostic - .additional_info() - .iter() - .map(|s| Cow::Borrowed(*s)) - .collect(); - Cow::Owned(infos) + diagnostic.info() } PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } => Cow::Borrowed(&[ @@ -296,25 +269,23 @@ impl Diagnostic for PublishDiagnostic { } } - fn docs_url(&self) -> Option { + fn docs_url(&self) -> Option> { match &self { - PublishDiagnostic::FastCheck(diagnostic) => { - Some(format!("https://jsr.io/go/{}", diagnostic.code())) - } + PublishDiagnostic::FastCheck(diagnostic) => diagnostic.docs_url(), PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic { ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } => None, }, PublishDiagnostic::InvalidPath { .. } => { - Some("https://jsr.io/go/invalid-path".to_owned()) - } - PublishDiagnostic::DuplicatePath { .. } => { - Some("https://jsr.io/go/case-insensitive-duplicate-path".to_owned()) + Some(Cow::Borrowed("https://jsr.io/go/invalid-path")) } + PublishDiagnostic::DuplicatePath { .. } => Some(Cow::Borrowed( + "https://jsr.io/go/case-insensitive-duplicate-path", + )), PublishDiagnostic::UnsupportedFileType { .. } => { - Some("https://jsr.io/go/unsupported-file-type".to_owned()) + Some(Cow::Borrowed("https://jsr.io/go/unsupported-file-type")) } PublishDiagnostic::InvalidExternalImport { .. } => { - Some("https://jsr.io/go/invalid-external-import".to_owned()) + Some(Cow::Borrowed("https://jsr.io/go/invalid-external-import")) } } } diff --git a/cli/tools/registry/graph.rs b/cli/tools/registry/graph.rs index d9fb665c4e..3445d55e7c 100644 --- a/cli/tools/registry/graph.rs +++ b/cli/tools/registry/graph.rs @@ -2,8 +2,10 @@ use std::collections::HashSet; use std::collections::VecDeque; +use std::sync::Arc; use deno_ast::ModuleSpecifier; +use deno_ast::SourceTextInfo; use deno_config::ConfigFile; use deno_config::WorkspaceConfig; use deno_core::anyhow::bail; @@ -76,7 +78,9 @@ pub fn collect_invalid_external_imports( let mut skip_specifiers: HashSet = HashSet::new(); let mut collect_if_invalid = - |skip_specifiers: &mut HashSet, resolution: &ResolutionResolved| { + |skip_specifiers: &mut HashSet, + text: &Arc, + resolution: &ResolutionResolved| { if visited.insert(resolution.specifier.clone()) { match resolution.specifier.scheme() { "file" | "data" | "node" => {} @@ -88,6 +92,7 @@ pub fn collect_invalid_external_imports( diagnostics_collector.push( PublishDiagnostic::InvalidExternalImport { kind: format!("non-JSR '{}'", resolution.specifier.scheme()), + text_info: SourceTextInfo::new(text.clone()), imported: resolution.specifier.clone(), referrer: resolution.range.clone(), }, @@ -98,6 +103,7 @@ pub fn collect_invalid_external_imports( diagnostics_collector.push( PublishDiagnostic::InvalidExternalImport { kind: format!("'{}'", resolution.specifier.scheme()), + text_info: SourceTextInfo::new(text.clone()), imported: resolution.specifier.clone(), referrer: resolution.range.clone(), }, @@ -128,10 +134,10 @@ pub fn collect_invalid_external_imports( for (_, dep) in &module.dependencies { if let Some(resolved) = dep.maybe_code.ok() { - collect_if_invalid(&mut skip_specifiers, resolved); + collect_if_invalid(&mut skip_specifiers, &module.source, resolved); } if let Some(resolved) = dep.maybe_type.ok() { - collect_if_invalid(&mut skip_specifiers, resolved); + collect_if_invalid(&mut skip_specifiers, &module.source, resolved); } } } @@ -144,7 +150,7 @@ pub fn collect_fast_check_type_graph_diagnostics( packages: &[MemberRoots], diagnostics_collector: &PublishDiagnosticsCollector, ) -> bool { - let mut seen_diagnostics = HashSet::new(); + let mut had_diagnostic = false; let mut seen_modules = HashSet::with_capacity(graph.specifiers_count()); for package in packages { let mut pending = VecDeque::new(); @@ -161,12 +167,9 @@ pub fn collect_fast_check_type_graph_diagnostics( let Some(es_module) = module.js() else { continue; }; - if let Some(diagnostic) = es_module.fast_check_diagnostic() { - for diagnostic in diagnostic.flatten_multiple() { - if !seen_diagnostics.insert(diagnostic.message_with_range_for_test()) - { - continue; - } + if let Some(diagnostics) = es_module.fast_check_diagnostics() { + for diagnostic in diagnostics { + had_diagnostic = true; diagnostics_collector .push(PublishDiagnostic::FastCheck(diagnostic.clone())); if matches!( @@ -197,5 +200,5 @@ pub fn collect_fast_check_type_graph_diagnostics( } } - !seen_diagnostics.is_empty() + had_diagnostic } diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs index 5f03fa6fd4..cfdec04c56 100644 --- a/cli/tools/registry/mod.rs +++ b/cli/tools/registry/mod.rs @@ -643,7 +643,6 @@ async fn publish_package( struct PreparePackagesData { publish_order_graph: PublishOrderGraph, - graph: Arc, package_by_name: HashMap>, } @@ -678,7 +677,7 @@ async fn prepare_packages_for_publishing( let package = prepare_publish( &deno_json, source_cache.clone(), - graph.clone(), + graph, import_map, diagnostics_collector, ) @@ -689,7 +688,6 @@ async fn prepare_packages_for_publishing( let package_by_name = HashMap::from([(package_name, package)]); return Ok(PreparePackagesData { publish_order_graph, - graph, package_by_name, }); }; @@ -743,7 +741,6 @@ async fn prepare_packages_for_publishing( } Ok(PreparePackagesData { publish_order_graph, - graph, package_by_name, }) } @@ -849,11 +846,7 @@ pub async fn publish( ) .await?; - let source_parser = LazyGraphSourceParser::new( - cli_factory.parsed_source_cache(), - &prepared_data.graph, - ); - diagnostics_collector.print_and_error(source_parser)?; + diagnostics_collector.print_and_error()?; if prepared_data.package_by_name.is_empty() { bail!("No packages to publish"); diff --git a/cli/tools/registry/tar.rs b/cli/tools/registry/tar.rs index e63a76516f..6543fbf2ec 100644 --- a/cli/tools/registry/tar.rs +++ b/cli/tools/registry/tar.rs @@ -206,7 +206,7 @@ fn resolve_content_maybe_unfurling( let text = String::from_utf8(data)?; deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.to_string(), + specifier: specifier.clone(), text_info: deno_ast::SourceTextInfo::from_string(text), media_type, capture_tokens: false, diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index e98f4b4305..a52eb095f6 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -16,14 +16,15 @@ use crate::tools::test::worker_has_tests; use crate::tools::test::TestEvent; use crate::tools::test::TestEventSender; +use deno_ast::diagnostics::Diagnostic; use deno_ast::swc::ast as swc_ast; use deno_ast::swc::common::comments::CommentKind; use deno_ast::swc::visit::noop_visit_type; use deno_ast::swc::visit::Visit; use deno_ast::swc::visit::VisitWith; -use deno_ast::DiagnosticsError; use deno_ast::ImportsNotUsedAsValues; use deno_ast::ModuleSpecifier; +use deno_ast::ParseDiagnosticsError; use deno_ast::ParsedSource; use deno_ast::SourcePos; use deno_ast::SourceRangedForSpanned; @@ -324,7 +325,7 @@ impl ReplSession { &mut self, line: &str, ) -> EvaluationOutput { - fn format_diagnostic(diagnostic: &deno_ast::Diagnostic) -> String { + fn format_diagnostic(diagnostic: &deno_ast::ParseDiagnostic) -> String { let display_position = diagnostic.display_position(); format!( "{}: {} at {}:{}", @@ -377,11 +378,11 @@ impl ReplSession { } Err(err) => { // handle a parsing diagnostic - match err.downcast_ref::() { + match err.downcast_ref::() { Some(diagnostic) => { Ok(EvaluationOutput::Error(format_diagnostic(diagnostic))) } - None => match err.downcast_ref::() { + None => match err.downcast_ref::() { Some(diagnostics) => Ok(EvaluationOutput::Error( diagnostics .0 @@ -786,13 +787,13 @@ fn parse_source_as( media_type: deno_ast::MediaType, ) -> Result { let specifier = if media_type == deno_ast::MediaType::Tsx { - "repl.tsx" + ModuleSpecifier::parse("file:///repl.tsx").unwrap() } else { - "repl.ts" + ModuleSpecifier::parse("file:///repl.ts").unwrap() }; let parsed = deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.to_string(), + specifier, text_info: deno_ast::SourceTextInfo::from_string(source), media_type, capture_tokens: true, diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 332bfa8c81..c138abec29 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -752,7 +752,7 @@ fn extract_files_from_source_comments( media_type: MediaType, ) -> Result, AnyError> { let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.to_string(), + specifier: specifier.clone(), text_info: deno_ast::SourceTextInfo::new(source), media_type, capture_tokens: false, diff --git a/cli/tools/test/reporters/pretty.rs b/cli/tools/test/reporters/pretty.rs index c49081dd66..4e8a1f402b 100644 --- a/cli/tools/test/reporters/pretty.rs +++ b/cli/tools/test/reporters/pretty.rs @@ -141,7 +141,7 @@ impl PrettyTestReporter { .child_results_buffer .entry(description.parent_id) .or_default() - .remove(&description.id); + .shift_remove(&description.id); } fn write_output_end(&mut self) { diff --git a/cli/tools/vendor/analyze.rs b/cli/tools/vendor/analyze.rs index c804fa1ce1..2b00f6bf47 100644 --- a/cli/tools/vendor/analyze.rs +++ b/cli/tools/vendor/analyze.rs @@ -61,6 +61,7 @@ fn export_specifier_has_default(s: &ExportSpecifier) -> bool { #[cfg(test)] mod test { use deno_ast::MediaType; + use deno_ast::ModuleSpecifier; use deno_ast::ParseParams; use deno_ast::ParsedSource; use deno_ast::SourceTextInfo; @@ -101,7 +102,7 @@ mod test { fn parse_module(text: &str) -> ParsedSource { deno_ast::parse_module(ParseParams { - specifier: "file:///mod.ts".to_string(), + specifier: ModuleSpecifier::parse("file:///mod.ts").unwrap(), capture_tokens: false, maybe_syntax: None, media_type: MediaType::TypeScript, diff --git a/cli/util/fs.rs b/cli/util/fs.rs index bab36b31ea..c81686f959 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -361,8 +361,7 @@ pub fn collect_specifiers( if path.is_dir() { result.push(PathOrPattern::Path(path)); } else if !files.exclude.matches_path(&path) { - let url = ModuleSpecifier::from_file_path(&path) - .map_err(|_| anyhow!("Invalid file path '{}'", path.display()))?; + let url = specifier_from_file_path(&path)?; prepared.push(url); } } @@ -385,7 +384,7 @@ pub fn collect_specifiers( .collect_file_patterns(files)?; let mut collected_files_as_urls = collected_files .iter() - .map(|f| ModuleSpecifier::from_file_path(f).unwrap()) + .map(|f| specifier_from_file_path(f).unwrap()) .collect::>(); collected_files_as_urls.sort(); @@ -703,6 +702,13 @@ impl LaxSingleProcessFsFlag { } } +pub fn specifier_from_file_path( + path: &Path, +) -> Result { + ModuleSpecifier::from_file_path(path) + .map_err(|_| anyhow!("Invalid file path '{}'", path.display())) +} + #[cfg(test)] mod tests { use super::*; diff --git a/cli/util/import_map.rs b/cli/util/import_map.rs index 2656389b8c..b8b8b9a1ae 100644 --- a/cli/util/import_map.rs +++ b/cli/util/import_map.rs @@ -4,6 +4,7 @@ use std::collections::HashSet; use deno_ast::ParsedSource; use deno_ast::SourceRange; +use deno_ast::SourceTextInfo; use deno_core::serde_json; use deno_core::ModuleSpecifier; use deno_graph::DefaultModuleAnalyzer; @@ -72,6 +73,7 @@ fn values_to_set<'a>( pub enum ImportMapUnfurlDiagnostic { UnanalyzableDynamicImport { specifier: ModuleSpecifier, + text_info: SourceTextInfo, range: SourceRange, }, } @@ -150,6 +152,7 @@ impl<'a> ImportMapUnfurler<'a> { ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { specifier: url.to_owned(), range: SourceRange::new(start_pos, end_pos), + text_info: parsed_source.text_info().clone(), }, ); } @@ -295,7 +298,7 @@ mod tests { fn parse_ast(specifier: &Url, source_code: &str) -> ParsedSource { let media_type = MediaType::from_specifier(specifier); deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.to_string(), + specifier: specifier.clone(), media_type, capture_tokens: false, maybe_syntax: None, diff --git a/runtime/shared.rs b/runtime/shared.rs index 35712dfb3b..04fcdcfdb7 100644 --- a/runtime/shared.rs +++ b/runtime/shared.rs @@ -84,7 +84,7 @@ pub fn maybe_transpile_source( let code = source.load()?; let parsed = deno_ast::parse_module(ParseParams { - specifier: source.specifier.to_string(), + specifier: deno_core::url::Url::parse(source.specifier).unwrap(), text_info: SourceTextInfo::from_string(code.as_str().to_owned()), media_type, capture_tokens: false,