From fbc9a65125f360e9b52e4379a6477663743d5c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 30 Oct 2024 00:56:58 +0100 Subject: [PATCH 01/14] fix(lint): skip linting minified files --- cli/tools/lint/linter.rs | 111 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index 2c2bc43acb..b2b62a8271 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -7,6 +7,7 @@ use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_ast::ParsedSource; use deno_ast::SourceTextInfo; +use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_graph::ModuleGraph; @@ -97,6 +98,14 @@ impl CliLinter { ext: Option<&str>, ) -> Result<(ParsedSource, Vec), AnyError> { let specifier = specifier_from_file_path(file_path)?; + + if is_minified_file(&source_code) { + bail!( + "{} appears to be a minified file, skipping linting", + specifier.as_str() + ); + } + let media_type = if let Some(ext) = ext { MediaType::from_str(&format!("placeholder.{ext}")) } else if file_path.extension().is_none() { @@ -257,3 +266,105 @@ fn apply_lint_fixes( deno_ast::apply_text_changes(text_info.text_str(), quick_fixes); Some(new_text) } + +#[derive(Debug, Default)] +pub struct FileMetrics { + avg_line_length: f64, + long_line_percentage: f64, + whitespace_ratio: f64, + has_source_map: bool, + short_var_name_ratio: f64, +} + +pub fn is_minified_file(code: &str) -> bool { + let mut metrics = FileMetrics::default(); + + // Split into non-empty lines + let lines: Vec<&str> = code + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .collect(); + + if lines.is_empty() { + return false; + } + + // Calculate average line length + let total_length: usize = lines.iter().map(|line| line.len()).sum(); + metrics.avg_line_length = total_length as f64 / lines.len() as f64; + + // Calculate percentage of long lines (>500 chars) + let long_lines = lines.iter().filter(|line| line.len() > 500).count(); + metrics.long_line_percentage = + (long_lines as f64 / lines.len() as f64) * 100.0; + + // Calculate whitespace ratio + let whitespace_count = code.chars().filter(|c| c.is_whitespace()).count(); + metrics.whitespace_ratio = whitespace_count as f64 / code.len() as f64; + + // Check for source map references + metrics.has_source_map = code.contains("//# sourceMappingURL="); + + // Calculate score + let mut score = 0.0; + + // Very long average line length is a strong indicator + if metrics.avg_line_length > 200.0 { + score += 3.0; + } + if metrics.avg_line_length > 500.0 { + score += 2.0; + } + + // High percentage of long lines + if metrics.long_line_percentage > 10.0 { + score += 2.0; + } + + // Low whitespace ratio is typical in minified files + if metrics.whitespace_ratio < 0.1 { + score += 2.0; + } + + // Presence of source maps is a good indicator + if metrics.has_source_map { + score += 1.0; + } + + // High ratio of short variable names + if metrics.short_var_name_ratio > 0.3 { + score += 2.0; + } + + score >= 5.0 +} + +// Example test module +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_minified_file() { + let minified = + "function f(a,b){return a.concat(b)}var x=function(n){return n+1};"; + let result = is_minified_file(minified); + assert!(result.is_minified); + } + + #[test] + fn test_normal_file() { + let normal = r#" + function concatenateArrays(array1, array2) { + return array1.concat(array2); + } + + const incrementNumber = function(number) { + return number + 1; + }; + "#; + let result = is_minified_file(normal); + assert!(!result.is_minified); + } +} From 643cae5a394d8211df04b2df01e6f670060bfd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 5 Nov 2024 02:50:04 +0100 Subject: [PATCH 02/14] do fewer allocations --- cli/tools/lint/linter.rs | 130 ++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 77 deletions(-) diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index b2b62a8271..b2cbb84f42 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -267,77 +267,35 @@ fn apply_lint_fixes( Some(new_text) } -#[derive(Debug, Default)] -pub struct FileMetrics { - avg_line_length: f64, - long_line_percentage: f64, - whitespace_ratio: f64, - has_source_map: bool, - short_var_name_ratio: f64, -} - pub fn is_minified_file(code: &str) -> bool { - let mut metrics = FileMetrics::default(); + const LONG_LINE_THRESHOLD: usize = 250; + const WHITESPACE_THRESHOLD: f64 = 0.2; - // Split into non-empty lines - let lines: Vec<&str> = code - .lines() - .map(str::trim) - .filter(|line| !line.is_empty()) - .collect(); + let is_possibly_minified = code.lines().any(|line| { + // Line length over the threshold suggests a minified file. + let line_len = line.len(); + if line_len > LONG_LINE_THRESHOLD { + return true; + } - if lines.is_empty() { - return false; - } + let line = line.trim(); + // So does a source map url. + if line.starts_with("//# sourceMappingURL=") { + return true; + } - // Calculate average line length - let total_length: usize = lines.iter().map(|line| line.len()).sum(); - metrics.avg_line_length = total_length as f64 / lines.len() as f64; + // Last ditch effort, if there's few whitespaces it's probably minified. + if line_len > 0 { + let whitespace_count = + line.chars().filter(|c| c.is_ascii_whitespace()).count(); + return (whitespace_count as f64 / line_len as f64) + < WHITESPACE_THRESHOLD; + } - // Calculate percentage of long lines (>500 chars) - let long_lines = lines.iter().filter(|line| line.len() > 500).count(); - metrics.long_line_percentage = - (long_lines as f64 / lines.len() as f64) * 100.0; + false + }); - // Calculate whitespace ratio - let whitespace_count = code.chars().filter(|c| c.is_whitespace()).count(); - metrics.whitespace_ratio = whitespace_count as f64 / code.len() as f64; - - // Check for source map references - metrics.has_source_map = code.contains("//# sourceMappingURL="); - - // Calculate score - let mut score = 0.0; - - // Very long average line length is a strong indicator - if metrics.avg_line_length > 200.0 { - score += 3.0; - } - if metrics.avg_line_length > 500.0 { - score += 2.0; - } - - // High percentage of long lines - if metrics.long_line_percentage > 10.0 { - score += 2.0; - } - - // Low whitespace ratio is typical in minified files - if metrics.whitespace_ratio < 0.1 { - score += 2.0; - } - - // Presence of source maps is a good indicator - if metrics.has_source_map { - score += 1.0; - } - - // High ratio of short variable names - if metrics.short_var_name_ratio > 0.3 { - score += 2.0; - } - - score >= 5.0 + is_possibly_minified } // Example test module @@ -346,25 +304,43 @@ mod tests { use super::*; #[test] - fn test_minified_file() { + fn test_minified_file_col_length() { + let minified = + "const LOREM_IPSUM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`"; + assert!(is_minified_file(minified)); + } + + #[test] + fn test_minified_file_whitespace() { let minified = "function f(a,b){return a.concat(b)}var x=function(n){return n+1};"; - let result = is_minified_file(minified); - assert!(result.is_minified); + assert!(is_minified_file(minified)); } #[test] fn test_normal_file() { let normal = r#" - function concatenateArrays(array1, array2) { - return array1.concat(array2); - } +function concatenateArrays(array1, array2) { + return array1.concat(array2); +} - const incrementNumber = function(number) { - return number + 1; - }; - "#; - let result = is_minified_file(normal); - assert!(!result.is_minified); +const incrementNumber = function(number) { + return number + 1; +};"#; + assert!(!is_minified_file(normal)); + } + + #[test] + fn test_minified_file_source_map() { + let normal = r#" +function concatenateArrays(array1, array2) { + return array1.concat(array2); +} + +const incrementNumber = function(number) { + return number + 1; +}; +//# sourceMappingURL=sourcefile.map.js"#; + assert!(is_minified_file(normal)); } } From 207ec468c121ee07b907357b171d23a06e20f098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 5 Nov 2024 14:05:18 +0100 Subject: [PATCH 03/14] wip --- cli/tools/lint/linter.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index b2cbb84f42..ee4ccb3eed 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -269,7 +269,10 @@ fn apply_lint_fixes( pub fn is_minified_file(code: &str) -> bool { const LONG_LINE_THRESHOLD: usize = 250; - const WHITESPACE_THRESHOLD: f64 = 0.2; + const WHITESPACE_THRESHOLD: f64 = 0.01; + + let mut whitespace_count = 0; + let mut total_len = 0; let is_possibly_minified = code.lines().any(|line| { // Line length over the threshold suggests a minified file. @@ -284,18 +287,23 @@ pub fn is_minified_file(code: &str) -> bool { return true; } - // Last ditch effort, if there's few whitespaces it's probably minified. + // Last ditch effort, keep track of whitespace count. if line_len > 0 { - let whitespace_count = + whitespace_count += line.chars().filter(|c| c.is_ascii_whitespace()).count(); - return (whitespace_count as f64 / line_len as f64) - < WHITESPACE_THRESHOLD; + total_len += line.len(); } false }); - is_possibly_minified + let whitespace_ratio = whitespace_count as f64 / total_len as f64; + eprintln!("whitespace_ration {}", whitespace_ratio); + if whitespace_ratio < WHITESPACE_THRESHOLD { + true + } else { + is_possibly_minified + } } // Example test module From 95206202898a3179ed6d3540ead18b601ec117a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 6 Nov 2024 18:13:56 +0100 Subject: [PATCH 04/14] wip --- cli/tools/lint/linter.rs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index ee4ccb3eed..1dd1549ecd 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -269,10 +269,11 @@ fn apply_lint_fixes( pub fn is_minified_file(code: &str) -> bool { const LONG_LINE_THRESHOLD: usize = 250; - const WHITESPACE_THRESHOLD: f64 = 0.01; + const WHITESPACE_THRESHOLD: f64 = 0.1; let mut whitespace_count = 0; let mut total_len = 0; + let mut line_count = 0; let is_possibly_minified = code.lines().any(|line| { // Line length over the threshold suggests a minified file. @@ -287,6 +288,7 @@ pub fn is_minified_file(code: &str) -> bool { return true; } + line_count += 1; // Last ditch effort, keep track of whitespace count. if line_len > 0 { whitespace_count += @@ -297,13 +299,17 @@ pub fn is_minified_file(code: &str) -> bool { false }); + if is_possibly_minified { + return true; + } + + eprintln!( + "whitespace count {} total_len {}", + whitespace_count, total_len + ); let whitespace_ratio = whitespace_count as f64 / total_len as f64; eprintln!("whitespace_ration {}", whitespace_ratio); - if whitespace_ratio < WHITESPACE_THRESHOLD { - true - } else { - is_possibly_minified - } + whitespace_ratio < WHITESPACE_THRESHOLD } // Example test module @@ -325,6 +331,15 @@ mod tests { assert!(is_minified_file(minified)); } + #[test] + fn test_minified_file_sourcemap() { + let minified = r#"function f(a, b) { return a.concat(b) } +var x = function(n) { return n + 1; }; +//# sourceMappingURL=sourcefile.map.js" +"#; + assert!(is_minified_file(minified)); + } + #[test] fn test_normal_file() { let normal = r#" @@ -339,7 +354,7 @@ const incrementNumber = function(number) { } #[test] - fn test_minified_file_source_map() { + fn test_normal_file_source_map() { let normal = r#" function concatenateArrays(array1, array2) { return array1.concat(array2); @@ -349,6 +364,6 @@ const incrementNumber = function(number) { return number + 1; }; //# sourceMappingURL=sourcefile.map.js"#; - assert!(is_minified_file(normal)); + assert!(!is_minified_file(normal)); } } From 66a152a0aced2f6390ab0367de69bd90a5cf8509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 7 Nov 2024 00:38:04 +0100 Subject: [PATCH 05/14] rework --- cli/tools/lint/linter.rs | 105 +------------------ cli/tools/lint/minified_file.rs | 172 ++++++++++++++++++++++++++++++++ cli/tools/lint/mod.rs | 1 + 3 files changed, 176 insertions(+), 102 deletions(-) create mode 100644 cli/tools/lint/minified_file.rs diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index 1dd1549ecd..165d717d9c 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -20,6 +20,7 @@ use deno_lint::linter::LinterOptions; use crate::util::fs::atomic_write_file_with_retries; use crate::util::fs::specifier_from_file_path; +use super::minified_file; use super::rules::FileOrPackageLintRule; use super::rules::PackageLintRule; use super::ConfiguredRules; @@ -99,7 +100,8 @@ impl CliLinter { ) -> Result<(ParsedSource, Vec), AnyError> { let specifier = specifier_from_file_path(file_path)?; - if is_minified_file(&source_code) { + let metrics = minified_file::analyze_content(&source_code); + if metrics.is_likely_minified() { bail!( "{} appears to be a minified file, skipping linting", specifier.as_str() @@ -266,104 +268,3 @@ fn apply_lint_fixes( deno_ast::apply_text_changes(text_info.text_str(), quick_fixes); Some(new_text) } - -pub fn is_minified_file(code: &str) -> bool { - const LONG_LINE_THRESHOLD: usize = 250; - const WHITESPACE_THRESHOLD: f64 = 0.1; - - let mut whitespace_count = 0; - let mut total_len = 0; - let mut line_count = 0; - - let is_possibly_minified = code.lines().any(|line| { - // Line length over the threshold suggests a minified file. - let line_len = line.len(); - if line_len > LONG_LINE_THRESHOLD { - return true; - } - - let line = line.trim(); - // So does a source map url. - if line.starts_with("//# sourceMappingURL=") { - return true; - } - - line_count += 1; - // Last ditch effort, keep track of whitespace count. - if line_len > 0 { - whitespace_count += - line.chars().filter(|c| c.is_ascii_whitespace()).count(); - total_len += line.len(); - } - - false - }); - - if is_possibly_minified { - return true; - } - - eprintln!( - "whitespace count {} total_len {}", - whitespace_count, total_len - ); - let whitespace_ratio = whitespace_count as f64 / total_len as f64; - eprintln!("whitespace_ration {}", whitespace_ratio); - whitespace_ratio < WHITESPACE_THRESHOLD -} - -// Example test module -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_minified_file_col_length() { - let minified = - "const LOREM_IPSUM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`"; - assert!(is_minified_file(minified)); - } - - #[test] - fn test_minified_file_whitespace() { - let minified = - "function f(a,b){return a.concat(b)}var x=function(n){return n+1};"; - assert!(is_minified_file(minified)); - } - - #[test] - fn test_minified_file_sourcemap() { - let minified = r#"function f(a, b) { return a.concat(b) } -var x = function(n) { return n + 1; }; -//# sourceMappingURL=sourcefile.map.js" -"#; - assert!(is_minified_file(minified)); - } - - #[test] - fn test_normal_file() { - let normal = r#" -function concatenateArrays(array1, array2) { - return array1.concat(array2); -} - -const incrementNumber = function(number) { - return number + 1; -};"#; - assert!(!is_minified_file(normal)); - } - - #[test] - fn test_normal_file_source_map() { - let normal = r#" -function concatenateArrays(array1, array2) { - return array1.concat(array2); -} - -const incrementNumber = function(number) { - return number + 1; -}; -//# sourceMappingURL=sourcefile.map.js"#; - assert!(!is_minified_file(normal)); - } -} diff --git a/cli/tools/lint/minified_file.rs b/cli/tools/lint/minified_file.rs new file mode 100644 index 0000000000..c015debd46 --- /dev/null +++ b/cli/tools/lint/minified_file.rs @@ -0,0 +1,172 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +#[derive(Debug)] +pub struct FileMetrics { + long_lines_count: usize, + total_lines: usize, + whitespace_ratio: f64, + has_license_comment: bool, +} + +impl FileMetrics { + #[inline] + pub fn is_likely_minified(&self) -> bool { + let long_lines_ratio = + self.long_lines_count as f64 / self.total_lines as f64; + + (long_lines_ratio > 0.3 || self.whitespace_ratio < 0.05) + && !(self.has_license_comment && self.total_lines < 5) + } +} + +pub fn analyze_content(content: &str) -> FileMetrics { + let mut total_lines = 0; + let mut long_lines_count = 0; + let mut whitespace_count = 0; + let mut total_chars = 0; + let mut has_license = false; + let mut in_multiline_comment = false; + + // Preallocate a line buffer to avoid per-line allocations + let mut line_buffer = String::with_capacity(1024); + + // Process the content character by character to avoid line allocations + let mut chars = content.chars().peekable(); + while let Some(c) = chars.next() { + total_chars += 1; + + if c.is_whitespace() { + whitespace_count += 1; + } + + line_buffer.push(c); + + // Check for end of line or end of content + if c == '\n' || chars.peek().is_none() { + total_lines += 1; + let trimmed = line_buffer.trim(); + + // Check for license/copyright only if we haven't found one yet + if !has_license && !trimmed.is_empty() { + // Avoid allocating a new string for case comparison + has_license = trimmed.chars().any(|c| c.is_ascii_alphabetic()) + && (trimmed.contains("license") + || trimmed.contains("LICENSE") + || trimmed.contains("copyright") + || trimmed.contains("COPYRIGHT") + || trimmed.contains("(c)") + || trimmed.contains("(C)")); + } + + // Handle comments without allocating new strings + if trimmed.starts_with("/*") { + in_multiline_comment = true; + } + if trimmed.ends_with("*/") { + in_multiline_comment = false; + line_buffer.clear(); + continue; + } + if in_multiline_comment || trimmed.starts_with("//") { + line_buffer.clear(); + continue; + } + + // Check line length + if line_buffer.len() > 250 { + long_lines_count += 1; + } + + line_buffer.clear(); + } + } + + // Handle case where file doesn't end with newline + if !line_buffer.is_empty() { + total_lines += 1; + } + + let whitespace_ratio = if total_chars > 0 { + whitespace_count as f64 / total_chars as f64 + } else { + 0.0 + }; + + FileMetrics { + long_lines_count, + total_lines, + whitespace_ratio, + has_license_comment: has_license, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_normal_js() { + let content = r#" +function hello() { + // This is a normal comment + console.log("Hello, world!"); +} + +// Another comment +const x = 42; + +/* Multi-line + comment */ +"#; + let metrics = analyze_content(content); + assert!(!metrics.is_likely_minified()); + } + + #[test] + fn test_minified_file_col_length() { + let content = + "const LOREM_IPSUM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`"; + let metrics = analyze_content(content); + assert!(metrics.is_likely_minified()); + } + + #[test] + fn test_minified_js() { + let content = "function hello(){console.log(\"Hello, world!\")}const x=42;function veryLongFunction(){return\"This is a very long line that exceeds 250 characters and contains lots of code and stuff and more code and even more stuff until we definitely exceed the limit we set for considering a line to be very long in our minification detection algorithm\"}"; + let metrics = analyze_content(content); + assert!(metrics.is_likely_minified()); + } + + #[test] + fn test_minified_file_whitespace() { + let content = + "function f(a,b){return a.concat(b)}var x=function(n){return n+1};"; + let metrics = analyze_content(content); + assert!(!metrics.is_likely_minified()); + } + + #[test] + fn test_license_only() { + let content = r#"/* +* Copyright (c) 2024 Example Corp. +* Licensed under MIT License +*/ +"#; + let metrics = analyze_content(content); + assert!(!metrics.is_likely_minified()); + } + + #[test] + fn test_normal_file() { + let content = r#" +function concatenateArrays(array1, array2) { + return array1.concat(array2); +} + +const incrementNumber = function(number) { + return number + 1; +};"#; + let metrics = analyze_content(content); + assert!(!metrics.is_likely_minified()); + } +} diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index e096b486ef..404297117e 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -52,6 +52,7 @@ use crate::util::path::is_script_ext; use crate::util::sync::AtomicFlag; mod linter; +mod minified_file; mod reporters; mod rules; From 336cda567a24b2a80bb61ab2ba282270bc83c852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 7 Nov 2024 00:47:21 +0100 Subject: [PATCH 06/14] add tests --- .dprint.json | 1 + cli/tools/lint/minified_file.rs | 7 ++++--- tests/specs/lint/minified/__test__.jsonc | 7 +++++++ tests/specs/lint/minified/minified.js | 5 +++++ 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 tests/specs/lint/minified/__test__.jsonc create mode 100644 tests/specs/lint/minified/minified.js diff --git a/.dprint.json b/.dprint.json index 7877f17647..d42ad4083c 100644 --- a/.dprint.json +++ b/.dprint.json @@ -43,6 +43,7 @@ "tests/specs/fmt", "tests/specs/lint/bom", "tests/specs/lint/default_ts", + "tests/specs/lint/minified", "tests/specs/lint/syntax_error_reporting", "tests/specs/publish/no_check_surfaces_syntax_error", "tests/specs/run/default_ts", diff --git a/cli/tools/lint/minified_file.rs b/cli/tools/lint/minified_file.rs index c015debd46..5bb3e60c87 100644 --- a/cli/tools/lint/minified_file.rs +++ b/cli/tools/lint/minified_file.rs @@ -14,12 +14,13 @@ impl FileMetrics { let long_lines_ratio = self.long_lines_count as f64 / self.total_lines as f64; - (long_lines_ratio > 0.3 || self.whitespace_ratio < 0.05) - && !(self.has_license_comment && self.total_lines < 5) + (long_lines_ratio >= 0.2 || self.whitespace_ratio < 0.05) + && !(self.has_license_comment && self.total_lines < 3) } } pub fn analyze_content(content: &str) -> FileMetrics { + const LONG_LINE_LEN: usize = 250; let mut total_lines = 0; let mut long_lines_count = 0; let mut whitespace_count = 0; @@ -73,7 +74,7 @@ pub fn analyze_content(content: &str) -> FileMetrics { } // Check line length - if line_buffer.len() > 250 { + if line_buffer.len() > LONG_LINE_LEN { long_lines_count += 1; } diff --git a/tests/specs/lint/minified/__test__.jsonc b/tests/specs/lint/minified/__test__.jsonc new file mode 100644 index 0000000000..5fb3162bdf --- /dev/null +++ b/tests/specs/lint/minified/__test__.jsonc @@ -0,0 +1,7 @@ +{ + "tests": { + "args": ["lint", "minified.js"], + "output": "minified.out", + "exitCode": 1 + } +} diff --git a/tests/specs/lint/minified/minified.js b/tests/specs/lint/minified/minified.js new file mode 100644 index 0000000000..5db24c1509 --- /dev/null +++ b/tests/specs/lint/minified/minified.js @@ -0,0 +1,5 @@ +/* +* Copyright (c) 2024 Example Corp. +* Licensed under MIT License +*/ +function f(a,b){return a.concat(b)}var x=function(n){return n+1};function g(a,b){return a.concat(b)}var x=function(n){return n+1};function h(a,b){return a.concat(b)}var x=function(n){return n+1};function i(a,b){return a.concat(b)}var x=function(n){return n+1};function j(a,b){return a.concat(b)}var x=function(n){return n+1}; \ No newline at end of file From 4d5f9028fd334e108d69990492fa7fe03a80872c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 14 Nov 2024 12:56:45 +0100 Subject: [PATCH 07/14] add lint result --- cli/tools/lint/linter.rs | 34 ++++++++++++++++------ cli/tools/lint/mod.rs | 61 ++++++++++++++++++++++++---------------- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index 165d717d9c..b0981b0d57 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -25,6 +25,18 @@ use super::rules::FileOrPackageLintRule; use super::rules::PackageLintRule; use super::ConfiguredRules; +pub enum LintResult { + /// File was linted and optionally produced diagnostics + Linted { + parsed_source: ParsedSource, + diagnostics: Vec, + }, + /// File was not parsed and linted because, eg. it might have + /// been a minified file. + #[allow(unused)] + Skipped, +} + pub struct CliLinterOptions { pub configured_rules: ConfiguredRules, pub fix: bool, @@ -97,15 +109,12 @@ impl CliLinter { file_path: &Path, source_code: String, ext: Option<&str>, - ) -> Result<(ParsedSource, Vec), AnyError> { + ) -> Result { let specifier = specifier_from_file_path(file_path)?; let metrics = minified_file::analyze_content(&source_code); if metrics.is_likely_minified() { - bail!( - "{} appears to be a minified file, skipping linting", - specifier.as_str() - ); + Ok(LintResult::Skipped); } let media_type = if let Some(ext) = ext { @@ -119,7 +128,7 @@ impl CliLinter { if self.fix { self.lint_file_and_fix(&specifier, media_type, source_code, file_path) } else { - self + let (parsed_source, diagnostics) = self .linter .lint_file(LintFileOptions { specifier, @@ -127,7 +136,11 @@ impl CliLinter { source_code, config: self.deno_lint_config.clone(), }) - .map_err(AnyError::from) + .map_err(AnyError::from)?; + Ok(LintResult::Linted { + parsed_source, + diagnostics, + }) } } @@ -137,7 +150,7 @@ impl CliLinter { media_type: MediaType, source_code: String, file_path: &Path, - ) -> Result<(ParsedSource, Vec), deno_core::anyhow::Error> { + ) -> Result { // initial lint let (source, diagnostics) = self.linter.lint_file(LintFileOptions { specifier: specifier.clone(), @@ -194,7 +207,10 @@ impl CliLinter { .context("Failed writing fix to file.")?; } - Ok((source, diagnostics)) + Ok(LintResult::Linted { + parsed_source: source, + diagnostics, + }) } } diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index b1d589ac34..048567834d 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -4,7 +4,6 @@ //! [`deno_lint`](https://github.com/denoland/deno_lint). use deno_ast::ModuleSpecifier; -use deno_ast::ParsedSource; use deno_config::deno_json::LintRulesConfig; use deno_config::glob::FileCollector; use deno_config::glob::FilePatterns; @@ -19,8 +18,8 @@ use deno_core::serde_json; use deno_core::unsync::future::LocalFutureExt; use deno_core::unsync::future::SharedLocal; use deno_graph::ModuleGraph; -use deno_lint::diagnostic::LintDiagnostic; use deno_lint::linter::LintConfig; +use linter::LintResult; use log::debug; use reporters::create_reporter; use reporters::LintReporter; @@ -371,14 +370,18 @@ impl WorkspaceLinter { file_text, cli_options.ext_flag().as_deref(), ); - if let Ok((file_source, file_diagnostics)) = &r { + if let Ok(LintResult::Linted { + parsed_source, + diagnostics, + }) = &r + { if let Some(incremental_cache) = &maybe_incremental_cache { - if file_diagnostics.is_empty() { + if diagnostics.is_empty() { // update the incremental cache if there were no diagnostics incremental_cache.update_file( &file_path, // ensure the returned text is used here as it may have been modified via --fix - file_source.text(), + parsed_source.text(), ) } } @@ -496,7 +499,7 @@ fn lint_stdin( file_path: &Path, configured_rules: ConfiguredRules, deno_lint_config: LintConfig, -) -> Result<(ParsedSource, Vec), AnyError> { +) -> Result { let mut source_code = String::new(); if stdin().read_to_string(&mut source_code).is_err() { return Err(generic_error("Failed to read from stdin")); @@ -515,33 +518,41 @@ fn lint_stdin( fn handle_lint_result( file_path: &str, - result: Result<(ParsedSource, Vec), AnyError>, + result: Result, reporter_lock: Arc>>, ) -> bool { let mut reporter = reporter_lock.lock(); match result { - Ok((source, mut file_diagnostics)) => { - if !source.diagnostics().is_empty() { - for parse_diagnostic in source.diagnostics() { - log::warn!("{}: {}", colors::yellow("warn"), parse_diagnostic); - } - } - file_diagnostics.sort_by(|a, b| match a.specifier.cmp(&b.specifier) { - std::cmp::Ordering::Equal => { - let a_start = a.range.as_ref().map(|r| r.range.start); - let b_start = b.range.as_ref().map(|r| r.range.start); - match a_start.cmp(&b_start) { - std::cmp::Ordering::Equal => a.details.code.cmp(&b.details.code), - other => other, + Ok(lint_result) => { + if let LintResult::Linted { + parsed_source, + mut diagnostics, + } = lint_result + { + if !parsed_source.diagnostics().is_empty() { + for parse_diagnostic in parsed_source.diagnostics() { + log::warn!("{}: {}", colors::yellow("warn"), parse_diagnostic); } } - file_order => file_order, - }); - for d in &file_diagnostics { - reporter.visit_diagnostic(d); + diagnostics.sort_by(|a, b| match a.specifier.cmp(&b.specifier) { + std::cmp::Ordering::Equal => { + let a_start = a.range.as_ref().map(|r| r.range.start); + let b_start = b.range.as_ref().map(|r| r.range.start); + match a_start.cmp(&b_start) { + std::cmp::Ordering::Equal => a.details.code.cmp(&b.details.code), + other => other, + } + } + file_order => file_order, + }); + for d in &diagnostics { + reporter.visit_diagnostic(d); + } + diagnostics.is_empty() + } else { + true } - file_diagnostics.is_empty() } Err(err) => { reporter.visit_error(file_path, &err); From 2a2da281df718f24cbb6f3fda71ae68fd890bd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 14 Nov 2024 14:41:00 +0100 Subject: [PATCH 08/14] wire up diagnostic --- cli/tools/lint/linter.rs | 20 ++++++++++++++++---- cli/tools/lint/mod.rs | 13 +++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index b0981b0d57..27f5e042a2 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -7,11 +7,11 @@ use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_ast::ParsedSource; use deno_ast::SourceTextInfo; -use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_graph::ModuleGraph; use deno_lint::diagnostic::LintDiagnostic; +use deno_lint::diagnostic::LintDiagnosticDetails; use deno_lint::linter::LintConfig as DenoLintConfig; use deno_lint::linter::LintFileOptions; use deno_lint::linter::Linter as DenoLintLinter; @@ -33,8 +33,7 @@ pub enum LintResult { }, /// File was not parsed and linted because, eg. it might have /// been a minified file. - #[allow(unused)] - Skipped, + Skipped { diagnostic: LintDiagnostic }, } pub struct CliLinterOptions { @@ -114,7 +113,20 @@ impl CliLinter { let metrics = minified_file::analyze_content(&source_code); if metrics.is_likely_minified() { - Ok(LintResult::Skipped); + let details = LintDiagnosticDetails { + message: "File was not linted, because it's minified".to_string(), + code: "".to_string(), + hint: None, + fixes: vec![], + custom_docs_url: None, + info: vec![], + }; + let diagnostic = LintDiagnostic { + specifier, + range: None, + details, + }; + return Ok(LintResult::Skipped { diagnostic }); } let media_type = if let Some(ext) = ext { diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index 048567834d..f3314815a5 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -524,12 +524,11 @@ fn handle_lint_result( let mut reporter = reporter_lock.lock(); match result { - Ok(lint_result) => { - if let LintResult::Linted { + Ok(lint_result) => match lint_result { + LintResult::Linted { parsed_source, mut diagnostics, - } = lint_result - { + } => { if !parsed_source.diagnostics().is_empty() { for parse_diagnostic in parsed_source.diagnostics() { log::warn!("{}: {}", colors::yellow("warn"), parse_diagnostic); @@ -550,10 +549,12 @@ fn handle_lint_result( reporter.visit_diagnostic(d); } diagnostics.is_empty() - } else { + } + LintResult::Skipped { diagnostic } => { + reporter.visit_diagnostic(&diagnostic); true } - } + }, Err(err) => { reporter.visit_error(file_path, &err); false From 61ea7e91a72af10a093071c229957bf0adbb9209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 14 Nov 2024 14:44:17 +0100 Subject: [PATCH 09/14] test --- tests/specs/lint/minified/__test__.jsonc | 8 +++----- tests/specs/lint/minified/minified.out | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 tests/specs/lint/minified/minified.out diff --git a/tests/specs/lint/minified/__test__.jsonc b/tests/specs/lint/minified/__test__.jsonc index 5fb3162bdf..6d71840d5a 100644 --- a/tests/specs/lint/minified/__test__.jsonc +++ b/tests/specs/lint/minified/__test__.jsonc @@ -1,7 +1,5 @@ { - "tests": { - "args": ["lint", "minified.js"], - "output": "minified.out", - "exitCode": 1 - } + "args": ["lint", "minified.js"], + "output": "minified.out", + "exitCode": 0 } diff --git a/tests/specs/lint/minified/minified.out b/tests/specs/lint/minified/minified.out new file mode 100644 index 0000000000..23a3a9d599 --- /dev/null +++ b/tests/specs/lint/minified/minified.out @@ -0,0 +1 @@ +asdfasdf \ No newline at end of file From a95ea7e14265ab84ca8b77c948b2550f2e240946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 14 Nov 2024 16:39:39 +0100 Subject: [PATCH 10/14] lint --- cli/tools/lint/linter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index 27f5e042a2..b3e57399bf 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -33,7 +33,7 @@ pub enum LintResult { }, /// File was not parsed and linted because, eg. it might have /// been a minified file. - Skipped { diagnostic: LintDiagnostic }, + Skipped { diagnostic: Box }, } pub struct CliLinterOptions { @@ -121,11 +121,11 @@ impl CliLinter { custom_docs_url: None, info: vec![], }; - let diagnostic = LintDiagnostic { + let diagnostic = Box::new(LintDiagnostic { specifier, range: None, details, - }; + }); return Ok(LintResult::Skipped { diagnostic }); } From 03ed40abad6b90b5996130ba148213a4e8eb8c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 30 Nov 2024 03:07:28 +0100 Subject: [PATCH 11/14] tests --- cli/tools/lint/linter.rs | 21 +++----------- cli/tools/lint/minified_file.rs | 38 ++++++++++++++++---------- cli/tools/lint/mod.rs | 10 +++++-- cli/tools/lint/reporters.rs | 21 ++++++++++++++ tests/specs/lint/minified/minified.out | 4 ++- 5 files changed, 59 insertions(+), 35 deletions(-) diff --git a/cli/tools/lint/linter.rs b/cli/tools/lint/linter.rs index b3e57399bf..f913d930b7 100644 --- a/cli/tools/lint/linter.rs +++ b/cli/tools/lint/linter.rs @@ -11,7 +11,6 @@ use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_graph::ModuleGraph; use deno_lint::diagnostic::LintDiagnostic; -use deno_lint::diagnostic::LintDiagnosticDetails; use deno_lint::linter::LintConfig as DenoLintConfig; use deno_lint::linter::LintFileOptions; use deno_lint::linter::Linter as DenoLintLinter; @@ -33,7 +32,7 @@ pub enum LintResult { }, /// File was not parsed and linted because, eg. it might have /// been a minified file. - Skipped { diagnostic: Box }, + Skipped { reason: String }, } pub struct CliLinterOptions { @@ -111,22 +110,10 @@ impl CliLinter { ) -> Result { let specifier = specifier_from_file_path(file_path)?; - let metrics = minified_file::analyze_content(&source_code); - if metrics.is_likely_minified() { - let details = LintDiagnosticDetails { - message: "File was not linted, because it's minified".to_string(), - code: "".to_string(), - hint: None, - fixes: vec![], - custom_docs_url: None, - info: vec![], - }; - let diagnostic = Box::new(LintDiagnostic { - specifier, - range: None, - details, + if minified_file::is_likely_minified(&source_code) { + return Ok(LintResult::Skipped { + reason: "The file is minified".to_string(), }); - return Ok(LintResult::Skipped { diagnostic }); } let media_type = if let Some(ext) = ext { diff --git a/cli/tools/lint/minified_file.rs b/cli/tools/lint/minified_file.rs index 5bb3e60c87..44c678bca3 100644 --- a/cli/tools/lint/minified_file.rs +++ b/cli/tools/lint/minified_file.rs @@ -19,7 +19,8 @@ impl FileMetrics { } } -pub fn analyze_content(content: &str) -> FileMetrics { +/// Analyze the content and tell if the file is most likely a minified file or not. +pub fn is_likely_minified(content: &str) -> bool { const LONG_LINE_LEN: usize = 250; let mut total_lines = 0; let mut long_lines_count = 0; @@ -28,6 +29,12 @@ pub fn analyze_content(content: &str) -> FileMetrics { let mut has_license = false; let mut in_multiline_comment = false; + // If total len of a file is shorter than the "long line" length, don't bother analyzing + // and consider non-minified. + if content.len() < LONG_LINE_LEN { + return false; + } + // Preallocate a line buffer to avoid per-line allocations let mut line_buffer = String::with_capacity(1024); @@ -93,12 +100,14 @@ pub fn analyze_content(content: &str) -> FileMetrics { 0.0 }; - FileMetrics { + let metrics = FileMetrics { long_lines_count, total_lines, whitespace_ratio, has_license_comment: has_license, - } + }; + + metrics.is_likely_minified() } #[cfg(test)] @@ -119,31 +128,32 @@ const x = 42; /* Multi-line comment */ "#; - let metrics = analyze_content(content); - assert!(!metrics.is_likely_minified()); + assert!(!is_likely_minified(content)); + } + + #[test] + fn empty_file() { + assert!(!is_likely_minified("")); } #[test] fn test_minified_file_col_length() { let content = "const LOREM_IPSUM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`"; - let metrics = analyze_content(content); - assert!(metrics.is_likely_minified()); + assert!(is_likely_minified(content)); } #[test] fn test_minified_js() { let content = "function hello(){console.log(\"Hello, world!\")}const x=42;function veryLongFunction(){return\"This is a very long line that exceeds 250 characters and contains lots of code and stuff and more code and even more stuff until we definitely exceed the limit we set for considering a line to be very long in our minification detection algorithm\"}"; - let metrics = analyze_content(content); - assert!(metrics.is_likely_minified()); + assert!(is_likely_minified(content)); } #[test] fn test_minified_file_whitespace() { let content = "function f(a,b){return a.concat(b)}var x=function(n){return n+1};"; - let metrics = analyze_content(content); - assert!(!metrics.is_likely_minified()); + assert!(!is_likely_minified(content)); } #[test] @@ -153,8 +163,7 @@ const x = 42; * Licensed under MIT License */ "#; - let metrics = analyze_content(content); - assert!(!metrics.is_likely_minified()); + assert!(!is_likely_minified(content)); } #[test] @@ -167,7 +176,6 @@ function concatenateArrays(array1, array2) { const incrementNumber = function(number) { return number + 1; };"#; - let metrics = analyze_content(content); - assert!(!metrics.is_likely_minified()); + assert!(!is_likely_minified(content)); } } diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index 93c8d797a6..eb60832634 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -552,8 +552,8 @@ fn handle_lint_result( } diagnostics.is_empty() } - LintResult::Skipped { diagnostic } => { - reporter.visit_diagnostic(&diagnostic); + LintResult::Skipped { reason } => { + reporter.visit_skipped(file_path, &reason); true } }, @@ -569,3 +569,9 @@ struct LintError { file_path: String, message: String, } + +#[derive(Serialize)] +struct LintSkipped { + file_path: String, + message: String, +} diff --git a/cli/tools/lint/reporters.rs b/cli/tools/lint/reporters.rs index 18bc1216a6..752f79147b 100644 --- a/cli/tools/lint/reporters.rs +++ b/cli/tools/lint/reporters.rs @@ -11,6 +11,7 @@ use serde::Serialize; use crate::args::LintReporterKind; use super::LintError; +use super::LintSkipped; const JSON_SCHEMA_VERSION: u8 = 1; @@ -24,6 +25,7 @@ pub fn create_reporter(kind: LintReporterKind) -> Box { pub trait LintReporter { fn visit_diagnostic(&mut self, d: &LintDiagnostic); + fn visit_skipped(&mut self, file_path: &str, reason: &str); fn visit_error(&mut self, file_path: &str, err: &AnyError); fn close(&mut self, check_count: usize); } @@ -52,6 +54,11 @@ impl LintReporter for PrettyLintReporter { log::error!("{}\n", d.display()); } + fn visit_skipped(&mut self, file_path: &str, reason: &str) { + log::info!("File was skipped: {file_path}"); + log::info!(" {reason}"); + } + fn visit_error(&mut self, file_path: &str, err: &AnyError) { log::error!("Error linting: {file_path}"); log::error!(" {err}"); @@ -113,6 +120,11 @@ impl LintReporter for CompactLintReporter { } } + fn visit_skipped(&mut self, file_path: &str, reason: &str) { + log::info!("File was skipped: {file_path}"); + log::info!(" {reason}"); + } + fn visit_error(&mut self, file_path: &str, err: &AnyError) { log::error!("Error linting: {file_path}"); log::error!(" {err}"); @@ -174,6 +186,7 @@ struct JsonLintDiagnostic { struct JsonLintReporter { version: u8, diagnostics: Vec, + skipped: Vec, errors: Vec, checked_files: Vec, } @@ -183,6 +196,7 @@ impl JsonLintReporter { JsonLintReporter { version: JSON_SCHEMA_VERSION, diagnostics: Vec::new(), + skipped: Vec::new(), errors: Vec::new(), checked_files: Vec::new(), } @@ -224,6 +238,13 @@ impl LintReporter for JsonLintReporter { } } + fn visit_skipped(&mut self, file_path: &str, reason: &str) { + self.skipped.push(LintSkipped { + file_path: file_path.to_string(), + message: reason.to_string(), + }); + } + fn visit_error(&mut self, file_path: &str, err: &AnyError) { self.errors.push(LintError { file_path: file_path.to_string(), diff --git a/tests/specs/lint/minified/minified.out b/tests/specs/lint/minified/minified.out index 23a3a9d599..b3fb1a6794 100644 --- a/tests/specs/lint/minified/minified.out +++ b/tests/specs/lint/minified/minified.out @@ -1 +1,3 @@ -asdfasdf \ No newline at end of file +File was skipped: [WILDCARD]minified.js + The file is minified +Checked 1 file From 8db506ffd491d9b1b549447291183d04aabe695b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 30 Nov 2024 03:47:04 +0100 Subject: [PATCH 12/14] fix --- tests/specs/lint/json/expected_json.out | 1 + tests/specs/lint/stdin_json/expected_from_stdin_json.out | 1 + .../with_report_config_override/with_report_config_override.out | 1 + 3 files changed, 3 insertions(+) diff --git a/tests/specs/lint/json/expected_json.out b/tests/specs/lint/json/expected_json.out index 242c47cf56..7db69f6f64 100644 --- a/tests/specs/lint/json/expected_json.out +++ b/tests/specs/lint/json/expected_json.out @@ -56,6 +56,7 @@ "hint": [WILDCARD] } ], + "skipped": [], "errors": [ { "file_path": "[WILDCARD]malformed.js", diff --git a/tests/specs/lint/stdin_json/expected_from_stdin_json.out b/tests/specs/lint/stdin_json/expected_from_stdin_json.out index 8db157bccb..094cd85101 100644 --- a/tests/specs/lint/stdin_json/expected_from_stdin_json.out +++ b/tests/specs/lint/stdin_json/expected_from_stdin_json.out @@ -20,6 +20,7 @@ "hint": [WILDCARD] } ], + "skipped": [], "errors": [], "checked_files": [ "[WILDCARD]main.ts" diff --git a/tests/specs/lint/with_report_config_override/with_report_config_override.out b/tests/specs/lint/with_report_config_override/with_report_config_override.out index fb873f187e..1e62a8f693 100644 --- a/tests/specs/lint/with_report_config_override/with_report_config_override.out +++ b/tests/specs/lint/with_report_config_override/with_report_config_override.out @@ -38,6 +38,7 @@ "hint": "If this is intentional, prefix it with an underscore like `_add`" } ], + "skipped": [], "errors": [], "checked_files": [ "[WILDCARD]a.ts" From 0db1c7965a2fd8acdc281a2b7bd036f0b3403918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 2 Jan 2025 11:23:57 +0100 Subject: [PATCH 13/14] lint --- cli/tools/lint/minified_file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/tools/lint/minified_file.rs b/cli/tools/lint/minified_file.rs index 44c678bca3..6eea54c223 100644 --- a/cli/tools/lint/minified_file.rs +++ b/cli/tools/lint/minified_file.rs @@ -1,4 +1,4 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright 2018-2025 the Deno authors. MIT license. #[derive(Debug)] pub struct FileMetrics { From 461c5c065998aa6e774b376312e03d611f263052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 3 Jan 2025 00:23:01 +0100 Subject: [PATCH 14/14] don't allocate --- cli/tools/lint/minified_file.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/cli/tools/lint/minified_file.rs b/cli/tools/lint/minified_file.rs index 6eea54c223..57047a4666 100644 --- a/cli/tools/lint/minified_file.rs +++ b/cli/tools/lint/minified_file.rs @@ -35,24 +35,23 @@ pub fn is_likely_minified(content: &str) -> bool { return false; } - // Preallocate a line buffer to avoid per-line allocations - let mut line_buffer = String::with_capacity(1024); + let mut str_ref = content; // Process the content character by character to avoid line allocations - let mut chars = content.chars().peekable(); - while let Some(c) = chars.next() { + let mut chars = content.chars().enumerate().peekable(); + while let Some((idx, c)) = chars.next() { total_chars += 1; if c.is_whitespace() { whitespace_count += 1; } - line_buffer.push(c); + str_ref = &content[..idx]; // Check for end of line or end of content if c == '\n' || chars.peek().is_none() { total_lines += 1; - let trimmed = line_buffer.trim(); + let trimmed = str_ref.trim(); // Check for license/copyright only if we haven't found one yet if !has_license && !trimmed.is_empty() { @@ -72,25 +71,25 @@ pub fn is_likely_minified(content: &str) -> bool { } if trimmed.ends_with("*/") { in_multiline_comment = false; - line_buffer.clear(); + str_ref = ""; continue; } if in_multiline_comment || trimmed.starts_with("//") { - line_buffer.clear(); + str_ref = ""; continue; } // Check line length - if line_buffer.len() > LONG_LINE_LEN { + if str_ref.len() > LONG_LINE_LEN { long_lines_count += 1; } - line_buffer.clear(); + str_ref = ""; } } // Handle case where file doesn't end with newline - if !line_buffer.is_empty() { + if !str_ref.is_empty() { total_lines += 1; }