Skip to content

Commit 31e4396

Browse files
vladimir-ivanovematipicoarendjr
authored
perf(biome_package): improve performance of biome_package (#6732)
Co-authored-by: Emanuele Stoppa <[email protected]> Co-authored-by: Arend van Beelen jr. <[email protected]>
1 parent 2649ac6 commit 31e4396

4 files changed

Lines changed: 78 additions & 49 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Resolved [#6281](https://github.com/biomejs/biome/issues/6281): Improved performance of handling `package.json` files in the scanner.

.github/workflows/benchmark.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,23 @@ on:
99
- next
1010
paths:
1111
- 'Cargo.lock'
12-
- 'crates/**_parser/**/*.rs'
13-
- 'crates/**_formatter/**/*.rs'
1412
- 'crates/**_analyze/**/*.rs'
13+
- 'crates/**_formatter/**/*.rs'
14+
- 'crates/**_parser/**/*.rs'
15+
- 'crates/biome_configuration/**/*.rs'
1516
- 'crates/biome_grit_patterns/**/*.rs'
17+
- 'crates/biome_package/**/*.rs'
1618
push:
1719
branches:
1820
- main
1921
paths:
2022
- 'Cargo.lock'
21-
- 'crates/**_parser/**/*.rs'
22-
- 'crates/**_formatter/**/*.rs'
2323
- 'crates/**_analyze/**/*.rs'
24+
- 'crates/**_formatter/**/*.rs'
25+
- 'crates/**_parser/**/*.rs'
26+
- 'crates/biome_configuration/**/*.rs'
2427
- 'crates/biome_grit_patterns/**/*.rs'
28+
- 'crates/biome_package/**/*.rs'
2529

2630
env:
2731
RUST_LOG: info

crates/biome_package/src/node_js_package/package_json.rs

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use biome_deserialize::{
77
use biome_diagnostics::Error;
88
use biome_json_parser::JsonParserOptions;
99
use biome_json_syntax::JsonLanguage;
10-
use biome_json_value::{JsonObject, JsonValue};
10+
use biome_json_value::JsonValue;
1111
use biome_text_size::TextRange;
1212
use camino::Utf8Path;
1313
use node_semver::{Range, SemverError};
@@ -36,7 +36,11 @@ pub struct PackageJson {
3636
pub optional_dependencies: Dependencies,
3737
pub license: Option<(Box<str>, TextRange)>,
3838

39-
pub(crate) raw_json: JsonObject,
39+
pub author: Option<String>,
40+
pub exports: Option<JsonValue>,
41+
pub imports: Option<JsonValue>,
42+
pub main: Option<String>,
43+
pub types: Option<String>,
4044
}
4145

4246
static_assertions::assert_impl_all!(PackageJson: Send, Sync);
@@ -58,9 +62,10 @@ impl PackageJson {
5862
}
5963

6064
pub fn with_exports(self, exports: impl Into<JsonValue>) -> Self {
61-
let mut raw_json = self.raw_json;
62-
raw_json.insert("exports".into(), exports.into());
63-
Self { raw_json, ..self }
65+
Self {
66+
exports: Some(exports.into()),
67+
..self
68+
}
6469
}
6570

6671
pub fn with_dependencies(self, dependencies: Dependencies) -> Self {
@@ -97,22 +102,6 @@ impl PackageJson {
97102

98103
false
99104
}
100-
101-
pub fn get_value_by_path(&self, path: &[&str]) -> Option<&JsonValue> {
102-
if path.is_empty() {
103-
return None;
104-
}
105-
106-
let mut value = self.raw_json.get(path[0])?;
107-
for key in path.iter().skip(1) {
108-
if let Some(inner_value) = value.as_object().and_then(|object| object.get(*key)) {
109-
value = inner_value;
110-
} else {
111-
return None;
112-
}
113-
}
114-
Some(value)
115-
}
116105
}
117106

118107
impl Manifest for PackageJson {
@@ -298,11 +287,33 @@ impl DeserializationVisitor for PackageJsonVisitor {
298287
"type" => {
299288
result.r#type = Deserializable::deserialize(ctx, &value, &key_text);
300289
}
301-
_ => {
290+
"author" => {
291+
if let Some(value) = Deserializable::deserialize(ctx, &value, &key_text) {
292+
result.author = Some(value);
293+
}
294+
}
295+
"exports" => {
296+
if let Some(value) = JsonValue::deserialize(ctx, &value, &key_text) {
297+
result.exports = Some(value);
298+
}
299+
}
300+
301+
"imports" => {
302302
if let Some(value) = JsonValue::deserialize(ctx, &value, &key_text) {
303-
result.raw_json.insert(key_text.into(), value);
303+
result.imports = Some(value);
304304
}
305305
}
306+
"types" => {
307+
if let Some(value) = Deserializable::deserialize(ctx, &value, &key_text) {
308+
result.types = Some(value);
309+
}
310+
}
311+
"main" => {
312+
if let Some(value) = Deserializable::deserialize(ctx, &value, &key_text) {
313+
result.main = Some(value);
314+
}
315+
}
316+
_ => {}
306317
}
307318
}
308319
Some(result)
@@ -370,10 +381,7 @@ mod tests {
370381
assert!(errors.is_empty());
371382

372383
let package_json = package_json.expect("parsing must have succeeded");
373-
assert_eq!(
374-
package_json.get_value_by_path(&["author"]),
375-
Some(&JsonValue::String(Text::Static("Biome Team").into()))
376-
);
384+
assert_eq!(package_json.author, Some("Biome Team".into()));
377385
}
378386

379387
#[test]

crates/biome_resolver/src/lib.rs

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,7 @@ fn resolve_import_alias(
241241
fs: &dyn ResolverFsProxy,
242242
options: &ResolveOptions,
243243
) -> Result<Utf8PathBuf, ResolveError> {
244-
let imports = package_json
245-
.get_value_by_path(&["imports"])
246-
.ok_or(ResolveError::NotFound)?;
244+
let imports = package_json.imports.clone().ok_or(ResolveError::NotFound)?;
247245
let imports = imports
248246
.as_object()
249247
.ok_or(ResolveError::InvalidMappingTarget)?;
@@ -260,9 +258,7 @@ fn resolve_export(
260258
fs: &dyn ResolverFsProxy,
261259
options: &ResolveOptions,
262260
) -> Result<Utf8PathBuf, ResolveError> {
263-
let exports = package_json
264-
.get_value_by_path(&["exports"])
265-
.ok_or(ResolveError::NotFound)?;
261+
let exports = &package_json.exports.clone().ok_or(ResolveError::NotFound)?;
266262

267263
match exports {
268264
JsonValue::Object(mapping) => {
@@ -553,30 +549,46 @@ fn resolve_package_path(
553549
};
554550

555551
if let Ok(package_json) = fs.read_package_json_in_directory(&package_path) {
556-
if package_json.get_value_by_path(&["exports"]).is_some() {
552+
if package_json.exports.is_some() {
557553
return resolve_export(subpath, &package_path, &package_json, fs, options);
558554
}
559555

560556
if subpath.is_empty() {
561-
let fallback_field = if options.resolve_types {
562-
"types"
563-
} else {
564-
"main"
565-
};
566-
567-
if let Some(main_target) = package_json
568-
.get_value_by_path(&[fallback_field])
569-
.and_then(JsonValue::as_string)
557+
if options.resolve_types {
558+
if let Some(value) =
559+
parse_package_json_field(fs, options, &package_path, package_json.types)
560+
{
561+
return value;
562+
}
563+
} else if let Some(value) =
564+
parse_package_json_field(fs, options, &package_path, package_json.main)
570565
{
571-
let options = options.without_extensions_or_manifests();
572-
return resolve_relative_path(main_target.as_str(), &package_path, fs, &options);
566+
return value;
573567
}
574568
}
575569
}
576570

577571
resolve_relative_path(subpath, &package_path, fs, options)
578572
}
579573

574+
fn parse_package_json_field(
575+
fs: &dyn ResolverFsProxy,
576+
options: &ResolveOptions,
577+
package_path: &Utf8Path,
578+
field: Option<String>,
579+
) -> Option<Result<Utf8PathBuf, ResolveError>> {
580+
if let Some(main_target) = field.as_ref() {
581+
let options = options.without_extensions_or_manifests();
582+
return Some(resolve_relative_path(
583+
main_target.as_str(),
584+
package_path,
585+
fs,
586+
&options,
587+
));
588+
}
589+
None
590+
}
591+
580592
enum ResolvedPathInfo {
581593
Directory,
582594
File,

0 commit comments

Comments
 (0)