From 0867b40e0026709ba17ecfca17901f14a9e3d4f6 Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Mon, 16 Dec 2024 03:21:31 +0100 Subject: [PATCH 01/21] fix(linter): fix configuration parser for `no-restricted-imports` (#7921) follow up from #7916 --- .../src/rules/eslint/no_restricted_imports.rs | 1536 +++++++++++++++-- .../eslint_no_restricted_imports.snap | 228 ++- 2 files changed, 1619 insertions(+), 145 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs index 9a881709cf4c1..a36e0081ac436 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -5,7 +5,11 @@ use rustc_hash::FxHashMap; use serde::Deserialize; use serde_json::Value; -use crate::{context::LintContext, module_record::ImportImportName, rule::Rule}; +use crate::{ + context::LintContext, + module_record::{ExportImportName, ImportImportName}, + rule::Rule, +}; fn no_restricted_imports_diagnostic( ctx: &LintContext, @@ -72,49 +76,69 @@ declare_oxc_lint!( nursery, ); +fn add_configuration_from_object( + paths: &mut Vec, + obj: &serde_json::Map, +) { + let Some(paths_value) = obj.get("paths") else { + return; + }; + + let Some(paths_array) = paths_value.as_array() else { + return; + }; + + for path_value in paths_array { + match path_value { + Value::String(module_name) => add_configuration_from_string(paths, module_name), + Value::Object(_) => { + if let Ok(mut path) = serde_json::from_value::(path_value.clone()) { + if let Some(import_names) = path.import_names { + path.import_names = Some( + import_names + .iter() + .map(|s| CompactStr::new(s)) + .collect::>() + .into_boxed_slice(), + ); + } + paths.push(path); + } + } + _ => (), + } + } +} + +fn add_configuration_from_string(paths: &mut Vec, module_name: &str) { + paths.push(RestrictedPath { + name: CompactStr::new(module_name), + import_names: None, + message: None, + }); +} + impl Rule for NoRestrictedImports { fn from_configuration(value: serde_json::Value) -> Self { let mut paths = Vec::new(); - match value { + + match &value { Value::Array(module_names) => { for module_name in module_names { - if let Some(module_name) = module_name.as_str() { - paths.push(RestrictedPath { - name: CompactStr::new(module_name), - import_names: None, - message: None, - }); - } + match module_name { + Value::String(module_string) => { + add_configuration_from_string(&mut paths, module_string); + } + Value::Object(obj) => add_configuration_from_object(&mut paths, obj), + _ => (), + }; } } Value::String(module_name) => { - paths.push(RestrictedPath { - name: CompactStr::new(module_name.as_str()), - import_names: None, - message: None, - }); + add_configuration_from_string(&mut paths, module_name); } Value::Object(obj) => { - if let Some(paths_value) = obj.get("paths") { - if let Some(paths_array) = paths_value.as_array() { - for path_value in paths_array { - if let Ok(mut path) = - serde_json::from_value::(path_value.clone()) - { - if let Some(import_names) = path.import_names { - path.import_names = Some( - import_names - .iter() - .map(|s| CompactStr::new(s)) - .collect::>() - .into_boxed_slice(), - ); - } - paths.push(path); - } - } - } - } + add_configuration_from_object(&mut paths, obj); } _ => {} } @@ -131,38 +155,42 @@ impl Rule for NoRestrictedImports { let source = entry.module_request.name(); let span = entry.module_request.span(); - if source == path.name.as_str() { - if let Some(import_names) = &path.import_names { - match &entry.import_name { - ImportImportName::Name(import) => { - let name = CompactStr::new(import.name()); + if source != path.name.as_str() { + continue; + } - if !import_names.contains(&name) { - no_restricted_imports_diagnostic( - ctx, - span, - path.message.clone(), - source, - ); - return; - } + if let Some(import_names) = &path.import_names { + match &entry.import_name { + ImportImportName::Name(import) => { + let name = CompactStr::new(import.name()); + + if import_names.contains(&name) { + no_restricted_imports_diagnostic( + ctx, + span, + path.message.clone(), + source, + ); + return; } - ImportImportName::Default(_) | ImportImportName::NamespaceObject => { - let name = CompactStr::new(entry.local_name.name()); - if !import_names.contains(&name) { - no_restricted_imports_diagnostic( - ctx, - span, - path.message.clone(), - source, - ); - return; - } + } + ImportImportName::Default(_) => return, + ImportImportName::NamespaceObject => { + let name = CompactStr::new(entry.local_name.name()); + + if import_names.contains(&name) { + no_restricted_imports_diagnostic( + ctx, + span, + path.message.clone(), + source, + ); + return; } } - } else { - no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); } + } else { + no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); } } @@ -175,7 +203,7 @@ impl Rule for NoRestrictedImports { } for (source, spans) in &side_effect_import_map { - if source.as_str() == path.name.as_str() { + if source.as_str() == path.name.as_str() && path.import_names.is_none() { if let Some(span) = spans.iter().next() { no_restricted_imports_diagnostic(ctx, *span, path.message.clone(), source); } @@ -199,10 +227,29 @@ impl Rule for NoRestrictedImports { let source = module_request.name(); let span = entry.span; - if source == path.name.as_str() { + if source != path.name.as_str() { + continue; + } + + if let Some(import_names) = &path.import_names { + match &entry.import_name { + ExportImportName::Name(import_name) + if import_names.contains(&import_name.name) => + { + no_restricted_imports_diagnostic( + ctx, + span, + path.message.clone(), + source, + ); + } + _ => (), + } + } else { no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); - return; } + + return; } } } @@ -213,124 +260,1355 @@ impl Rule for NoRestrictedImports { fn test() { use crate::tester::Tester; + let pass_disallowed_object_foo = serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# + }] + }]); + let pass = vec![ - // Basic cases - no matches + (r#"import os from "os";"#, None), + (r#"import os from "os";"#, Some(serde_json::json!(["osx"]))), + (r#"import fs from "fs";"#, Some(serde_json::json!(["crypto"]))), + (r#"import path from "path";"#, Some(serde_json::json!(["crypto", "stream", "os"]))), + (r#"import async from "async";"#, None), + (r#"import "foo""#, Some(serde_json::json!(["crypto"]))), + (r#"import "foo/bar";"#, Some(serde_json::json!(["foo"]))), ( - r#"import os from "os";"#, - Some(serde_json::json!({ - "paths": [{ "name": "fs" }] - })), + r#"import withPaths from "foo/bar";"#, + Some(serde_json::json!([{ "paths": ["foo", "bar"] }])), ), ( - r#"import fs from "fs";"#, - Some(serde_json::json!({ - "paths": [{ "name": "crypto" }] - })), + r#"import withPatterns from "foo/bar";"#, + Some(serde_json::json!([{ "patterns": ["foo/c*"] }])), ), + ("import foo from 'foo';", Some(serde_json::json!(["../foo"]))), + ("import foo from 'foo';", Some(serde_json::json!([{ "paths": ["../foo"] }]))), + ("import foo from 'foo';", Some(serde_json::json!([{ "patterns": ["../foo"] }]))), + ("import foo from 'foo';", Some(serde_json::json!(["/foo"]))), + ("import foo from 'foo';", Some(serde_json::json!([{ "paths": ["/foo"] }]))), + ("import relative from '../foo';", None), + ("import relative from '../foo';", Some(serde_json::json!(["../notFoo"]))), ( - r#"import path from "path";"#, - Some(serde_json::json!({ - "paths": [ - { "name": "crypto" }, - { "name": "stream" }, - { "name": "os" } - ] - })), + "import relativeWithPaths from '../foo';", + Some(serde_json::json!([{ "paths": ["../notFoo"] }])), + ), + ( + "import relativeWithPatterns from '../foo';", + Some(serde_json::json!([{ "patterns": ["notFoo"] }])), + ), + ("import absolute from '/foo';", None), + ("import absolute from '/foo';", Some(serde_json::json!(["/notFoo"]))), + ( + "import absoluteWithPaths from '/foo';", + Some(serde_json::json!([{ "paths": ["/notFoo"] }])), + ), + ( + "import absoluteWithPatterns from '/foo';", + Some(serde_json::json!([{ "patterns": ["notFoo"] }])), + ), + ( + r#"import withPatternsAndPaths from "foo/bar";"#, + Some(serde_json::json!([{ "paths": ["foo"], "patterns": ["foo/c*"] }])), + ), + ( + r#"import withGitignores from "foo/bar";"#, + Some(serde_json::json!([{ "patterns": ["foo/*", "!foo/bar"] }])), + ), + ( + r#"import withPatterns from "foo/bar";"#, + Some( + serde_json::json!([{ "patterns": [{ "group": ["foo/*", "!foo/bar"], "message": "foo is forbidden, use bar instead" }] }]), + ), + ), + ( + "import withPatternsCaseSensitive from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["FOO"], + "message": "foo is forbidden, use bar instead", + "caseSensitive": true + }] + }])), ), - // Testing with import names ( r#"import AllowedObject from "foo";"#, - Some(serde_json::json!({ + Some(serde_json::json!([{ "paths": [{ "name": "foo", - "importNames": ["AllowedObject"] + "importNames": ["DisallowedObject"] }] - })), + }])), ), - // Testing relative paths ( - "import relative from '../foo';", - Some(serde_json::json!({ - "paths": [{ "name": "../notFoo" }] - })), + r#"import DisallowedObject from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"] + }] + }])), + ), + ( + r#"import * as DisallowedObject from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "bar", + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# + }] + }])), + ), + ( + r#"import { AllowedObject } from "foo";"#, + Some(serde_json::json!(pass_disallowed_object_foo.clone())), + ), + ( + r#"import { 'AllowedObject' as bar } from "foo";"#, + Some(serde_json::json!(pass_disallowed_object_foo.clone())), + ), + ( + r#"import { ' ' as bar } from "foo";"#, + Some(serde_json::json!([{"paths": [{"name": "foo","importNames": [""]}]}])), + ), + ( + r#"import { '' as bar } from "foo";"#, + Some(serde_json::json!([{"paths": [{"name": "foo","importNames": [" "]}]}])), + ), + ( + r#"import { DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "bar", + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# + }] + }])), + ), + ( + r#"import { AllowedObject as DisallowedObject } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import { 'AllowedObject' as DisallowedObject } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import { AllowedObject, AllowedObjectTwo } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import { AllowedObject, AllowedObjectTwo as DisallowedObject } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import AllowedObjectThree, { AllowedObject as AllowedObjectTwo } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import AllowedObject, { AllowedObjectTwo as DisallowedObject } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), ), - // Multiple restricted imports ( - r#"import { DisallowedObjectOne, DisallowedObjectTwo } from "foo";"#, - Some(serde_json::json!({ + r#"import AllowedObject, { AllowedObjectTwo as DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ "paths": [{ "name": "foo", - "importNames": ["DisallowedObjectOne", "DisallowedObjectTwo"], + "importNames": ["DisallowedObject", "DisallowedObjectTwo"], + "message": r#"Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead."# + }] + }])), + ), + ( + r#"import AllowedObject, * as DisallowedObject from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject", "DisallowedObjectTwo"], + "message": r#"Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead."# + }] + }])), + ), + // ( + // r#"import { + // AllowedObject, + // DisallowedObject, // eslint-disable-line + // } from "foo";"#, + // Some( + // serde_json::json!([{ "paths": [{ "name": "foo", "importNames": ["DisallowedObject"] }] }]), + // ), + // ), + (r#"export * from "foo";"#, Some(serde_json::json!(["bar"]))), + ( + r#"export * from "foo";"#, + Some(serde_json::json!([{ + "name": "bar", + "importNames": ["DisallowedObject"] + }])), + ), + ( + r#"export { 'AllowedObject' } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"] + }] + }])), + ), + ( + r#"export { 'AllowedObject' as DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"] + }] + }])), + ), + ( + "import { Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNames": ["Foo"] + }] + }])), + ), + ( + "import Foo from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNames": ["Foo"] + }] + }])), + ), + ( + "import Foo from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import Foo from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Foo"], + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import Foo from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { Bar as Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { Bar as Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Foo"], + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import Foo, { Baz as Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^(Foo|Bar)" + }] + }])), + ), + ( + "import Foo, { Baz as Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Foo"], + "group": ["**/my/relative-module"], + "importNamePattern": "^Bar" + }] + }])), + ), + ( + "export { Bar } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "export { Bar as Foo } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + // ( + // r#"import { AllowedObject } from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "allowImportNames": ["AllowedObject"], + // "message": r#"Please import anything except "AllowedObject" from /bar/ instead."# + // }] + // }])), + // ), + // ( + // "import { foo } from 'foo';", + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "allowImportNames": ["foo"] + // }] + // }])), + // ), + // ( + // "import { foo } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "allowImportNames": ["foo"] + // }] + // }])), + // ), + // ( + // "export { bar } from 'foo';", + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "allowImportNames": ["bar"] + // }] + // }])), + // ), + // ( + // "export { bar } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "allowImportNames": ["bar"] + // }] + // }])), + // ), + ( + "import { Foo } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "allowImportNamePattern": "^Foo" + }] + }])), + ), + ( + r#"import withPatterns from "foo/bar";"#, + Some( + serde_json::json!([{ "patterns": [{ "regex": "foo/(?!bar)", "message": "foo is forbidden, use bar instead" }] }]), + ), + ), + ( + "import withPatternsCaseSensitive from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "regex": "FOO", + "message": "foo is forbidden, use bar instead", + "caseSensitive": true + }] + }])), + ), + ( + "import Foo from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "regex": "my/relative-module", + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "regex": "my/relative-module", + "importNamePattern": "^Foo" }] - })), + }])), ), ]; let fail = vec![ - // Basic restrictions + (r#"import "fs""#, Some(serde_json::json!(["fs"]))), + (r#"import os from "os";"#, Some(serde_json::json!(["fs", "crypto ", "stream", "os"]))), + (r#"import "foo/bar";"#, Some(serde_json::json!(["foo/bar"]))), ( - r#"import "fs""#, - Some(serde_json::json!({ - "paths": [{ "name": "fs" }] - })), + r#"import withPaths from "foo/bar";"#, + Some(serde_json::json!([{ "paths": ["foo/bar"] }])), + ), + // ( + // r#"import withPatterns from "foo/bar";"#, + // Some(serde_json::json!([{ "patterns": ["foo"] }])), + // ), + // ( + // r#"import withPatterns from "foo/bar";"#, + // Some(serde_json::json!([{ "patterns": ["bar"] }])), + // ), + // ( + // r#"import withPatterns from "foo/baz";"#, + // Some( + // serde_json::json!([{ "patterns": [{ "group": ["foo/*", "!foo/bar"], "message": "foo is forbidden, use foo/bar instead" }] }]), + // ), + // ), + // ( + // r#"import withPatterns from "foo/baz";"#, + // Some( + // serde_json::json!([{ "patterns": [{ "group": ["foo/bar", "foo/baz"], "message": "some foo subimports are restricted" }] }]), + // ), + // ), + // ( + // r#"import withPatterns from "foo/bar";"#, + // Some(serde_json::json!([{ "patterns": [{ "group": ["foo/bar"] }] }])), + // ), + // ( + // "import withPatternsCaseInsensitive from 'foo';", + // Some(serde_json::json!([{ "patterns": [{ "group": ["FOO"] }] }])), + // ), + // ( + // r#"import withGitignores from "foo/bar";"#, + // Some(serde_json::json!([{ "patterns": ["foo/*", "!foo/baz"] }])), + // ), + // (r#"export * from "fs";"#, Some(serde_json::json!(["fs"]))), + (r#"export * as ns from "fs";"#, Some(serde_json::json!(["fs"]))), + (r#"export {a} from "fs";"#, Some(serde_json::json!(["fs"]))), + ( + r#"export {foo as b} from "fs";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "fs", + "importNames": ["foo"], + "message": r#"Don"t import "foo"."# + }] + }])), + ), + ( + r#"export {"foo" as b} from "fs";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "fs", + "importNames": ["foo"], + "message": r#"Don"t import "foo"."# + }] + }])), ), - // With custom message ( - r#"import withGitignores from "foo";"#, - Some(serde_json::json!({ + r#"export {"foo"} from "fs";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "fs", + "importNames": ["foo"], + "message": r#"Don"t import "foo"."# + }] + }])), + ), + ( + r#"export {'๐Ÿ‘'} from "fs";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "fs", + "importNames": ["๐Ÿ‘"], + "message": r#"Don"t import "๐Ÿ‘"."# + }] + }])), + ), + ( + r#"export {''} from "fs";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "fs", + "importNames":[""], + "message": r#"Don"t import ""."# + }] + }])), + ), + // ( + // r#"export * as ns from "fs";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "fs", + // "importNames": ["foo"], + // "message": r#"Don"t import "foo"."# + // }] + // }])), + // ), + // ( + // r#"import withGitignores from "foo";"#, + // Some(serde_json::json!([{ + // "name": "foo", + // "message": r#"Please import from "bar" instead."# + // }])), + // ), + // ( + // r#"import withGitignores from "bar";"#, + // Some(serde_json::json!([ + // "foo", + // { + // "name": "bar", + // "message": r#"Please import from "baz" instead."# + // }, + // "baz" + // ])), + // ), + // ( + // r#"import withGitignores from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "message": r#"Please import from "bar" instead."# + // }] + // }])), + // ), + // ( + // r#"import DisallowedObject from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["default"], + // "message": r#"Please import the default import of "foo" from /bar/ instead."# + // }] + // }])), + // ), + // ( + // r#"import * as All from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["DisallowedObject"], + // "message": r#"Please import "DisallowedObject" from /bar/ instead."# + // }] + // }])), + // ), + // ( + // r#"export * from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["DisallowedObject"], + // "message": r#"Please import "DisallowedObject" from /bar/ instead."# + // }] + // }])), + // ), + // ( + // r#"export * from "foo";"#, + // Some(serde_json::json!([{ + // "name": "", + // "importNames": ["DisallowedObject1, DisallowedObject2"] + // }])), + // ), + ( + r#"import { DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ "paths": [{ "name": "foo", - "message": "Please import from 'bar' instead." + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# }] - })), + }])), ), - // Restricting default import ( - r#"import DisallowedObject from "foo";"#, - Some(serde_json::json!({ + r#"import { DisallowedObject as AllowedObject } from "foo";"#, + Some(serde_json::json!([{ "paths": [{ "name": "foo", - "importNames": ["default"], - "message": "Please import the default import of 'foo' from /bar/ instead." + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# }] - })), + }])), ), - // Namespace imports ( - r#"import * as All from "foo";"#, - Some(serde_json::json!({ + r#"import { 'DisallowedObject' as AllowedObject } from "foo";"#, + Some(serde_json::json!([{ "paths": [{ "name": "foo", "importNames": ["DisallowedObject"], - "message": "Please import 'DisallowedObject' from /bar/ instead." + "message": r#"Please import "DisallowedObject" from /bar/ instead."# }] - })), + }])), ), - // Export restrictions ( - r#"export { something } from "fs";"#, - Some(serde_json::json!({ - "paths": [{ "name": "fs" }] - })), + r#"import { '๐Ÿ‘' as bar } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["๐Ÿ‘"] + }] + }])), + ), + ( + r#"import { '' as bar } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": [""] + }] + }])), + ), + ( + r#"import { AllowedObject, DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# + }] + }])), ), - // Complex case with multiple restrictions ( - r#"import { foo, bar, baz } from "mod""#, - Some(serde_json::json!({ + r#"import { AllowedObject, DisallowedObject as AllowedObjectTwo } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# + }] + }])), + ), + ( + r#"import { AllowedObject, DisallowedObject as AllowedObjectTwo } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObjectTwo", "DisallowedObject"], + "message": r#"Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead."# + }] + }])), + ), + ( + r#"import { AllowedObject, DisallowedObject as AllowedObjectTwo } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject", "DisallowedObjectTwo"], + "message": r#"Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead."# + }] + }])), + ), + // ( + // r#"import DisallowedObject, { AllowedObject as AllowedObjectTwo } from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["default"], + // "message": r#"Please import the default import of "foo" from /bar/ instead."# + // }] + // }])), + // ), + // ( + // r#"import AllowedObject, { DisallowedObject as AllowedObjectTwo } from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["DisallowedObject"], + // "message": r#"Please import "DisallowedObject" from /bar/ instead."# + // }] + // }])), + // ), + // ( + // r#"import AllowedObject, * as AllowedObjectTwo from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["DisallowedObject"], + // "message": r#"Please import "DisallowedObject" from /bar/ instead."# + // }] + // }])), + // ), + // ( + // r#"import AllowedObject, * as AllowedObjectTwo from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["DisallowedObject", "DisallowedObjectTwo"], + // "message": r#"Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead."# + // }] + // }])), + // ), + // ( + // r#"import { DisallowedObjectOne, DisallowedObjectTwo, AllowedObject } from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["DisallowedObjectOne", "DisallowedObjectTwo"] + // }] + // }])), + // ), + // ( + // r#"import { DisallowedObjectOne, DisallowedObjectTwo, AllowedObject } from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["DisallowedObjectOne", "DisallowedObjectTwo"], + // "message": "Please import this module from /bar/ instead." + // }] + // }])), + // ), + // ( + // r#"import { AllowedObject, DisallowedObject as Bar } from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "importNames": ["DisallowedObject"] + // }] + // }])), + // ), + // ( + // "import foo, { bar } from 'mod';", + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "mod", + // "importNames": ["bar"] + // }] + // }])), + // ), + ( + "import { Image, Text, ScrollView } from 'react-native'", + Some(serde_json::json!([{ + "paths": [{ + "name": "react-native", + "importNames": ["Text"], + "message": "import Text from ui/_components instead" + }, { + "name": "react-native", + "importNames": ["TextInput"], + "message": "import TextInput from ui/_components instead" + }, { + "name": "react-native", + "importNames": ["View"], + "message": "import View from ui/_components instead " + },{ + "name": "react-native", + "importNames": ["ScrollView"], + "message": "import ScrollView from ui/_components instead" + },{ + "name": "react-native", + "importNames": ["KeyboardAvoidingView"], + "message": "import KeyboardAvoidingView from ui/_components instead" + }, { + "name": "react-native", + "importNames": ["ImageBackground"], + "message": "import ImageBackground from ui/_components instead" + }, { + "name": "react-native", + "importNames": ["Image"], + "message": "import Image from ui/_components instead" + }] + }])), + ), + ( + "import { foo, bar, baz } from 'mod'", + Some(serde_json::json!([{ + "paths": [{ + "name": "mod", + "importNames": ["foo"], + "message": "Import foo from qux instead." + }, { + "name": "mod", + "importNames": ["baz"], + "message": "Import baz from qux instead." + }] + }])), + ), + ( + "import { foo, bar, baz, qux } from 'mod'", + Some(serde_json::json!([{ + "paths": [{ + "name": "mod", + "importNames": ["bar"], + "message": "Use `barbaz` instead of `bar`." + }, { + "name": "mod", + "importNames": ["foo", "qux"], + "message": r#"Don"t use "foo" and `qux` from "mod"."# + }] + }])), + ), + ( + "import { foo, bar, baz, qux } from 'mod'", + Some(serde_json::json!([{ + "paths": [{ + "name": "mod", + "importNames": ["foo", "baz"], + "message": r#"Don"t use "foo" or "baz" from "mod"."# + }, { + "name": "mod", + "importNames": ["a", "c"], + "message": r#"Don"t use "a" or "c" from "mod"."# + }, { + "name": "mod", + "importNames": ["b", "bar"], + "message": r#"Use "b" or `bar` from "quux/mod" instead."# + }] + }])), + ), + // ( + // "import * as mod from 'mod'", + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "mod", + // "importNames": ["foo"], + // "message": "Import foo from qux instead." + // }, { + // "name": "mod", + // "importNames": ["bar"], + // "message": "Import bar from qux instead." + // }] + // }])), + // ), + ( + "import { foo } from 'mod'", + Some(serde_json::json!([{ "paths": [ + // restricts importing anything from the module + { + "name": "mod" + }, + // message for a specific import name { "name": "mod", - "importNames": ["foo"], - "message": "Import foo from qux instead." + "importNames": ["bar"], + "message": "Import bar from qux instead." + } + ] + }])), + ), + ( + "import { bar } from 'mod'", + Some(serde_json::json!([{ + "paths": [ + // restricts importing anything from the module + { + "name": "mod" }, + // message for a specific import name { "name": "mod", - "importNames": ["baz"], - "message": "Import baz from qux instead." + "importNames": ["bar"], + "message": "Import bar from qux instead." } ] - })), + }])), + ), + // ( + // "import foo, { bar } from 'mod';", + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "mod", + // "importNames": ["default"] + // }] + // }])), + // ), + // ( + // "import foo, * as bar from 'mod';", + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "mod", + // "importNames": ["default"] + // }] + // }])), + // ), + ("import * as bar from 'foo';", Some(serde_json::json!(["foo"]))), + ( + "import { a, a as b } from 'mod';", + Some(serde_json::json!([{ + "paths": [{ + "name": "mod", + "importNames": ["a"] + }] + }])), + ), + ( + "export { x as y, x as z } from 'mod';", + Some(serde_json::json!([{ + "paths": [{ + "name": "mod", + "importNames": ["x"] + }] + }])), + ), + // ( + // "import foo, { default as bar } from 'mod';", + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "mod", + // "importNames": ["default"] + // }] + // }])), + // ), + ("import relative from '../foo';", Some(serde_json::json!(["../foo"]))), + ( + "import relativeWithPaths from '../foo';", + Some(serde_json::json!([{ "paths": ["../foo"] }])), ), + // ( + // "import relativeWithPatterns from '../foo';", + // Some(serde_json::json!([{ "patterns": ["../foo"] }])), + // ), + ("import absolute from '/foo';", Some(serde_json::json!(["/foo"]))), + ("import absoluteWithPaths from '/foo';", Some(serde_json::json!([{ "paths": ["/foo"] }]))), + // ( + // "import absoluteWithPatterns from '/foo';", + // Some(serde_json::json!([{ "patterns": ["foo"] }])), + // ), + // ( + // "import absoluteWithPatterns from '#foo/bar';", + // Some(serde_json::json!([{ "patterns": ["\\#foo"] }])), + // ), + // ( + // "import { Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNames": ["Foo"] + // }] + // }])), + // ), + // ( + // "import { Foo, Bar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNames": ["Foo", "Bar"], + // "message": "Import from @/utils instead." + // }] + // }])), + // ), + // ( + // "import * as All from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNames": ["Foo"] + // }] + // }])), + // ), + // ( + // "import * as AllWithCustomMessage from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNames": ["Foo"], + // "message": "Import from @/utils instead." + // }] + // }])), + // ), + // ( + // "import def, * as ns from 'mod';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["mod"], + // "importNames": ["default"] + // }] + // }])), + // ), + // ( + // "import Foo from 'mod';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["mod"], + // "importNames": ["default"] + // }] + // }])), + // ), + // ( + // "import { Foo } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { Foo as Bar } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import Foo, { Bar } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^(Foo|Bar)" + // }] + // }])), + // ), + // ( + // "import { Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { FooBar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import Foo, { Bar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo|^Bar" + // }] + // }])), + // ), + // ( + // "import { Foo, Bar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^(Foo|Bar)" + // }] + // }])), + // ), + // ( + // "import * as Foo from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import * as All from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import * as AllWithCustomMessage from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo", + // "message": "Import from @/utils instead." + // }] + // }])), + // ), + // ( + // "import * as AllWithCustomMessage from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Foo"], + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo", + // "message": "Import from @/utils instead." + // }] + // }])), + // ), + // ( + // "import { Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Foo"], + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Foo", "Bar"], + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Bar"], + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Foo"], + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Bar" + // }] + // }])), + // ), + // ( + // "import { Foo, Bar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Foo"], + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Bar" + // }] + // }])), + // ), + // ( + // "export { Foo } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "export { Foo as Bar } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "export { Foo } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Bar"], + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "export * from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "export { Bar } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "allowImportNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "export { Bar } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "allowImportNamePattern": "^Foo", + // "message": r#"Only imports that match the pattern "/^Foo/u" are allowed to be imported from "foo"."# + // }] + // }])), + // ), + // ( + // r#"import { AllowedObject, DisallowedObject } from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "allowImportNames": ["AllowedObject"] + // }] + // }])), + // ), + // ( + // r#"import { AllowedObject, DisallowedObject } from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "allowImportNames": ["AllowedObject"], + // "message": r#"Only "AllowedObject" is allowed to be imported from "foo"."# + // }] + // }])), + // ), + // ( + // r#"import { AllowedObject, DisallowedObject } from "foo";"#, + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "allowImportNames": ["AllowedObject"] + // }] + // }])), + // ), + // ( + // r#"import { AllowedObject, DisallowedObject } from "foo";"#, + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "allowImportNames": ["AllowedObject"], + // "message": r#"Only "AllowedObject" is allowed to be imported from "foo"."# + // }] + // }])), + // ), + // ( + // r#"import * as AllowedObject from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "allowImportNames": ["AllowedObject"] + // }] + // }])), + // ), + // ( + // r#"import * as AllowedObject from "foo";"#, + // Some(serde_json::json!([{ + // "paths": [{ + // "name": "foo", + // "allowImportNames": ["AllowedObject"], + // "message": r#"Only "AllowedObject" is allowed to be imported from "foo"."# + // }] + // }])), + // ), + // ( + // r#"import * as AllowedObject from "foo/bar";"#, + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo/*"], + // "allowImportNames": ["AllowedObject"] + // }] + // }])), + // ), + // ( + // r#"import * as AllowedObject from "foo/bar";"#, + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo/*"], + // "allowImportNames": ["AllowedObject"], + // "message": r#"Only "AllowedObject" is allowed to be imported from "foo"."# + // }] + // }])), + // ), + // ( + // r#"import * as AllowedObject from "foo/bar";"#, + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo/*"], + // "allowImportNamePattern": "^Allow" + // }] + // }])), + // ), + // ( + // r#"import * as AllowedObject from "foo/bar";"#, + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo/*"], + // "allowImportNamePattern": "^Allow", + // "message": r#"Only import names starting with "Allow" are allowed to be imported from "foo"."# + // }] + // }])), + // ), + // ( + // r#"import withPatterns from "foo/baz";"#, + // Some( + // serde_json::json!([{ "patterns": [{ "regex": "foo/(?!bar)", "message": "foo is forbidden, use bar instead" }] }]), + // ), + // ), + // ( + // "import withPatternsCaseSensitive from 'FOO';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "regex": "FOO", + // "message": "foo is forbidden, use bar instead", + // "caseSensitive": true + // }] + // }])), + // ), + // ( + // "import { Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "regex": "my/relative-module", + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import withPatternsCaseSensitive from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["FOO"], + // "message": "foo is forbidden, use bar instead", + // "caseSensitive": false + // }] + // }])), + // ), + // ( + // " + // // error + // import { Foo_Enum } from '@app/api'; + // import { Bar_Enum } from '@app/api/bar'; + // import { Baz_Enum } from '@app/api/baz'; + // import { B_Enum } from '@app/api/enums/foo'; + // + // // no error + // import { C_Enum } from '@app/api/enums'; + // ", + // Some(serde_json::json!([{ + // "patterns": [{ + // "regex": "@app/(?!(api/enums$)).*", + // "importNamePattern": "_Enum$" + // }] + // }])), + // ), ]; Tester::new(NoRestrictedImports::NAME, NoRestrictedImports::CATEGORY, pass, fail) diff --git a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap index 1af015ef61a63..8c3df6537d68c 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap @@ -9,37 +9,233 @@ snapshot_kind: text โ•ฐโ”€โ”€โ”€โ”€ help: Remove the import statement. - โš  eslint(no-restricted-imports): Please import from 'bar' instead. - โ•ญโ”€[no_restricted_imports.tsx:1:28] - 1 โ”‚ import withGitignores from "foo"; - ยท โ”€โ”€โ”€โ”€โ”€ + โš  eslint(no-restricted-imports): 'os' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:16] + 1 โ”‚ import os from "os"; + ยท โ”€โ”€โ”€โ”€ โ•ฐโ”€โ”€โ”€โ”€ help: Remove the import statement. - โš  eslint(no-restricted-imports): Please import the default import of 'foo' from /bar/ instead. - โ•ญโ”€[no_restricted_imports.tsx:1:30] - 1 โ”‚ import DisallowedObject from "foo"; - ยท โ”€โ”€โ”€โ”€โ”€ + โš  eslint(no-restricted-imports): 'foo/bar' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:8] + 1 โ”‚ import "foo/bar"; + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ•ฐโ”€โ”€โ”€โ”€ help: Remove the import statement. - โš  eslint(no-restricted-imports): Please import 'DisallowedObject' from /bar/ instead. - โ•ญโ”€[no_restricted_imports.tsx:1:22] - 1 โ”‚ import * as All from "foo"; - ยท โ”€โ”€โ”€โ”€โ”€ + โš  eslint(no-restricted-imports): 'foo/bar' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:23] + 1 โ”‚ import withPaths from "foo/bar"; + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ•ฐโ”€โ”€โ”€โ”€ help: Remove the import statement. โš  eslint(no-restricted-imports): 'fs' import is restricted from being used. - โ•ญโ”€[no_restricted_imports.tsx:1:10] - 1 โ”‚ export { something } from "fs"; - ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ญโ”€[no_restricted_imports.tsx:1:1] + 1 โ”‚ export * as ns from "fs"; + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): 'fs' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:9] + 1 โ”‚ export {a} from "fs"; + ยท โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Don"t import "foo". + โ•ญโ”€[no_restricted_imports.tsx:1:9] + 1 โ”‚ export {foo as b} from "fs"; + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Don"t import "foo". + โ•ญโ”€[no_restricted_imports.tsx:1:9] + 1 โ”‚ export {"foo" as b} from "fs"; + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Don"t import "foo". + โ•ญโ”€[no_restricted_imports.tsx:1:9] + 1 โ”‚ export {"foo"} from "fs"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Don"t import "๐Ÿ‘". + โ•ญโ”€[no_restricted_imports.tsx:1:9] + 1 โ”‚ export {'๐Ÿ‘'} from "fs"; + ยท โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Don"t import "". + โ•ญโ”€[no_restricted_imports.tsx:1:9] + 1 โ”‚ export {''} from "fs"; + ยท โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Please import "DisallowedObject" from /bar/ instead. + โ•ญโ”€[no_restricted_imports.tsx:1:34] + 1 โ”‚ import { DisallowedObject } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Please import "DisallowedObject" from /bar/ instead. + โ•ญโ”€[no_restricted_imports.tsx:1:51] + 1 โ”‚ import { DisallowedObject as AllowedObject } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Please import "DisallowedObject" from /bar/ instead. + โ•ญโ”€[no_restricted_imports.tsx:1:53] + 1 โ”‚ import { 'DisallowedObject' as AllowedObject } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): 'foo' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:31] + 1 โ”‚ import { '๐Ÿ‘' as bar } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): 'foo' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:27] + 1 โ”‚ import { '' as bar } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Please import "DisallowedObject" from /bar/ instead. + โ•ญโ”€[no_restricted_imports.tsx:1:49] + 1 โ”‚ import { AllowedObject, DisallowedObject } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Please import "DisallowedObject" from /bar/ instead. + โ•ญโ”€[no_restricted_imports.tsx:1:69] + 1 โ”‚ import { AllowedObject, DisallowedObject as AllowedObjectTwo } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead. + โ•ญโ”€[no_restricted_imports.tsx:1:69] + 1 โ”‚ import { AllowedObject, DisallowedObject as AllowedObjectTwo } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead. + โ•ญโ”€[no_restricted_imports.tsx:1:69] + 1 โ”‚ import { AllowedObject, DisallowedObject as AllowedObjectTwo } from "foo"; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): import Text from ui/_components instead + โ•ญโ”€[no_restricted_imports.tsx:1:41] + 1 โ”‚ import { Image, Text, ScrollView } from 'react-native' + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ•ฐโ”€โ”€โ”€โ”€ help: Remove the import statement. โš  eslint(no-restricted-imports): Import foo from qux instead. โ•ญโ”€[no_restricted_imports.tsx:1:31] - 1 โ”‚ import { foo, bar, baz } from "mod" + 1 โ”‚ import { foo, bar, baz } from 'mod' ยท โ”€โ”€โ”€โ”€โ”€ โ•ฐโ”€โ”€โ”€โ”€ help: Remove the import statement. + + โš  eslint(no-restricted-imports): Use `barbaz` instead of `bar`. + โ•ญโ”€[no_restricted_imports.tsx:1:36] + 1 โ”‚ import { foo, bar, baz, qux } from 'mod' + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Don"t use "foo" or "baz" from "mod". + โ•ญโ”€[no_restricted_imports.tsx:1:36] + 1 โ”‚ import { foo, bar, baz, qux } from 'mod' + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): 'mod' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:21] + 1 โ”‚ import { foo } from 'mod' + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): 'mod' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:21] + 1 โ”‚ import { bar } from 'mod' + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): Import bar from qux instead. + โ•ญโ”€[no_restricted_imports.tsx:1:21] + 1 โ”‚ import { bar } from 'mod' + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): 'foo' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:22] + 1 โ”‚ import * as bar from 'foo'; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): 'mod' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:27] + 1 โ”‚ import { a, a as b } from 'mod'; + ยท โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): 'mod' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:10] + 1 โ”‚ export { x as y, x as z } from 'mod'; + ยท โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): '../foo' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:22] + 1 โ”‚ import relative from '../foo'; + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): '../foo' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:31] + 1 โ”‚ import relativeWithPaths from '../foo'; + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): '/foo' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:22] + 1 โ”‚ import absolute from '/foo'; + ยท โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. + + โš  eslint(no-restricted-imports): '/foo' import is restricted from being used. + โ•ญโ”€[no_restricted_imports.tsx:1:31] + 1 โ”‚ import absoluteWithPaths from '/foo'; + ยท โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Remove the import statement. From e2b623328421ca9fc413f8301ef0c602320a4bae Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Mon, 16 Dec 2024 03:25:26 +0100 Subject: [PATCH 02/21] refactor(language_server): add capabilities struct (#7906) vscode test from local, intellij test from https://github.com/oxc-project/oxc/pull/7445#issuecomment-2496950216 --- .../oxc_language_server/src/capabilities.rs | 132 ++++++++++++++++++ crates/oxc_language_server/src/main.rs | 53 ++----- 2 files changed, 140 insertions(+), 45 deletions(-) create mode 100644 crates/oxc_language_server/src/capabilities.rs diff --git a/crates/oxc_language_server/src/capabilities.rs b/crates/oxc_language_server/src/capabilities.rs new file mode 100644 index 0000000000000..5dd476b3d93ec --- /dev/null +++ b/crates/oxc_language_server/src/capabilities.rs @@ -0,0 +1,132 @@ +use tower_lsp::lsp_types::{ + ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, OneOf, + ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions, + WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, +}; + +pub const CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC: CodeActionKind = + CodeActionKind::new("source.fixAll.oxc"); + +pub struct Capabilities { + pub code_action_provider: bool, +} + +impl From for Capabilities { + fn from(value: ClientCapabilities) -> Self { + // check if the client support some code action literal support + let code_action_provider = value.text_document.is_some_and(|capability| { + capability.code_action.is_some_and(|code_action| { + code_action.code_action_literal_support.is_some_and(|literal_support| { + !literal_support.code_action_kind.value_set.is_empty() + }) + }) + }); + + Self { code_action_provider } + } +} + +impl From for ServerCapabilities { + fn from(value: Capabilities) -> Self { + Self { + text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)), + workspace: Some(WorkspaceServerCapabilities { + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: Some(OneOf::Left(true)), + }), + file_operations: None, + }), + code_action_provider: if value.code_action_provider { + Some(CodeActionProviderCapability::Options(CodeActionOptions { + code_action_kinds: Some(vec![ + CodeActionKind::QUICKFIX, + CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, + ]), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + resolve_provider: None, + })) + } else { + None + }, + ..ServerCapabilities::default() + } + } +} + +#[cfg(test)] +mod test { + use tower_lsp::lsp_types::{ + ClientCapabilities, CodeActionClientCapabilities, CodeActionKindLiteralSupport, + CodeActionLiteralSupport, TextDocumentClientCapabilities, + }; + + use super::Capabilities; + + #[test] + fn test_code_action_provider_vscode() { + let client_capabilities = ClientCapabilities { + text_document: Some(TextDocumentClientCapabilities { + code_action: Some(CodeActionClientCapabilities { + code_action_literal_support: Some(CodeActionLiteralSupport { + code_action_kind: CodeActionKindLiteralSupport { + // this is from build (see help, about): + // Version: 1.95.3 (user setup) + // Commit: f1a4fb101478ce6ec82fe9627c43efbf9e98c813 + value_set: vec![ + #[allow(clippy::manual_string_new)] + "".into(), + "quickfix".into(), + "refactor".into(), + "refactor.extract".into(), + "refactor.inline".into(), + "refactor.rewrite".into(), + "source".into(), + "source.organizeImports".into(), + ], + }, + }), + ..CodeActionClientCapabilities::default() + }), + ..TextDocumentClientCapabilities::default() + }), + ..ClientCapabilities::default() + }; + + let capabilities = Capabilities::from(client_capabilities); + + assert!(capabilities.code_action_provider); + } + + #[test] + fn test_code_action_provider_intellij() { + let client_capabilities = ClientCapabilities { + text_document: Some(TextDocumentClientCapabilities { + code_action: Some(CodeActionClientCapabilities { + code_action_literal_support: Some(CodeActionLiteralSupport { + code_action_kind: CodeActionKindLiteralSupport { + // this is from build (see help, about): + // Build #IU-243.22562.145, built on December 8, 2024 + value_set: vec![ + "quickfix".into(), + #[allow(clippy::manual_string_new)] + "".into(), + "source".into(), + "refactor".into(), + ], + }, + }), + ..CodeActionClientCapabilities::default() + }), + ..TextDocumentClientCapabilities::default() + }), + ..ClientCapabilities::default() + }; + + let capabilities = Capabilities::from(client_capabilities); + + assert!(capabilities.code_action_provider); + } +} diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index b0d067bbbe6e1..1d5f4dac47c18 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -11,22 +11,21 @@ use tokio::sync::{Mutex, OnceCell, RwLock, SetError}; use tower_lsp::{ jsonrpc::{Error, ErrorCode, Result}, lsp_types::{ - CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams, - CodeActionProviderCapability, CodeActionResponse, ConfigurationItem, Diagnostic, - DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams, - DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, - InitializeParams, InitializeResult, InitializedParams, NumberOrString, OneOf, Position, - Range, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, - TextEdit, Url, WorkDoneProgressOptions, WorkspaceEdit, WorkspaceFoldersServerCapabilities, - WorkspaceServerCapabilities, + CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse, + ConfigurationItem, Diagnostic, DidChangeConfigurationParams, DidChangeTextDocumentParams, + DidChangeWatchedFilesParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, + DidSaveTextDocumentParams, InitializeParams, InitializeResult, InitializedParams, + NumberOrString, Position, Range, ServerInfo, TextEdit, Url, WorkspaceEdit, }, Client, LanguageServer, LspService, Server, }; use oxc_linter::{FixKind, LinterBuilder, Oxlintrc}; +use crate::capabilities::{Capabilities, CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC}; use crate::linter::{DiagnosticReport, ServerLinter}; +mod capabilities; mod linter; type FxDashMap = DashMap; @@ -88,9 +87,6 @@ enum SyntheticRunLevel { OnType, } -const CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC: CodeActionKind = - CodeActionKind::new("source.fixAll.oxc"); - #[tower_lsp::async_trait] impl LanguageServer for Backend { async fn initialize(&self, params: InitializeParams) -> Result { @@ -106,45 +102,12 @@ impl LanguageServer for Backend { *self.options.lock().await = value; } - // check if the client support some code action literal support - let code_action_provider = if params.capabilities.text_document.is_some_and(|capability| { - capability.code_action.is_some_and(|code_action| { - code_action.code_action_literal_support.is_some_and(|literal_support| { - !literal_support.code_action_kind.value_set.is_empty() - }) - }) - }) { - Some(CodeActionProviderCapability::Options(CodeActionOptions { - code_action_kinds: Some(vec![ - CodeActionKind::QUICKFIX, - CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, - ]), - work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, - resolve_provider: None, - })) - } else { - None - }; - let oxlintrc = self.init_linter_config().await; self.init_ignore_glob(oxlintrc).await; Ok(InitializeResult { server_info: Some(ServerInfo { name: "oxc".into(), version: None }), offset_encoding: None, - capabilities: ServerCapabilities { - text_document_sync: Some(TextDocumentSyncCapability::Kind( - TextDocumentSyncKind::FULL, - )), - workspace: Some(WorkspaceServerCapabilities { - workspace_folders: Some(WorkspaceFoldersServerCapabilities { - supported: Some(true), - change_notifications: Some(OneOf::Left(true)), - }), - file_operations: None, - }), - code_action_provider, - ..ServerCapabilities::default() - }, + capabilities: Capabilities::from(params.capabilities).into(), }) } From 523d48ccb25b4e32c906173ccfbc2dc25f90e81b Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:26:50 +0000 Subject: [PATCH 03/21] test(transformer): move named test to exports folder (#7922) I made a mistake, I should have put it in the `exports` folder and not `export` --- crates/oxc_semantic/src/builder.rs | 4 ++-- .../oxc/ts/{export => exports}/named/type-and-non-type.snap | 0 .../oxc/ts/{export => exports}/named/type-and-non-type.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/oxc_semantic/tests/fixtures/oxc/ts/{export => exports}/named/type-and-non-type.snap (100%) rename crates/oxc_semantic/tests/fixtures/oxc/ts/{export => exports}/named/type-and-non-type.ts (100%) diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 10fe077bb014b..80dfd277fd2a6 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -1868,12 +1868,12 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { if let Some(declaration) = &it.declaration { self.visit_declaration(declaration); } - let prev_reference_flags = self.current_reference_flags; + // let prev_reference_flags = self.current_reference_flags; if it.export_kind.is_type() { self.current_reference_flags = ReferenceFlags::Type; } self.visit_export_specifiers(&it.specifiers); - self.current_reference_flags = prev_reference_flags; + // self.current_reference_flags = prev_reference_flags; if let Some(source) = &it.source { self.visit_string_literal(source); diff --git a/crates/oxc_semantic/tests/fixtures/oxc/ts/export/named/type-and-non-type.snap b/crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/type-and-non-type.snap similarity index 100% rename from crates/oxc_semantic/tests/fixtures/oxc/ts/export/named/type-and-non-type.snap rename to crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/type-and-non-type.snap diff --git a/crates/oxc_semantic/tests/fixtures/oxc/ts/export/named/type-and-non-type.ts b/crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/type-and-non-type.ts similarity index 100% rename from crates/oxc_semantic/tests/fixtures/oxc/ts/export/named/type-and-non-type.ts rename to crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/type-and-non-type.ts From 596aead0e9cfef174e69fb44a783ebb9346cf553 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:26:52 +0000 Subject: [PATCH 04/21] fix(semantic): reset references flags when resolved (#7923) For this case, we set `current_reference_flags` to `ReferenceFlags::Type` for `TSInterfaceHeritage`, but never unset it, which causes resolving `fowardRef` identifier reuse `current_reference_flags` of `TSInterfaceHeritage`. ```ts import { forwardRef } from "react"; export interface MenuTriggerProps extends Object {} export const MenuTrigger = forwardRef(); ``` In this PR, reset the `current_reference_flags` when resolved, so that we don't need to reset it in individual visit functions. This is a reasonable change because the `current_reference_flags` only applies to the next encountered identifier. --- crates/oxc_semantic/src/builder.rs | 21 +++----- .../ts/exports/named/interface-heritage.snap | 50 +++++++++++++++++++ .../ts/exports/named/interface-heritage.ts | 3 ++ .../snapshots/semantic_typescript.snap | 6 --- 4 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.snap create mode 100644 crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.ts diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 80dfd277fd2a6..e6ece6045ed9d 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -1,6 +1,9 @@ //! Semantic Builder -use std::cell::{Cell, RefCell}; +use std::{ + cell::{Cell, RefCell}, + mem, +}; use rustc_hash::FxHashMap; @@ -1789,7 +1792,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { fn visit_simple_assignment_target(&mut self, it: &SimpleAssignmentTarget<'a>) { let kind = AstKind::SimpleAssignmentTarget(self.alloc(it)); self.enter_node(kind); - let prev_reference_flags = self.current_reference_flags; // Except that the read-write flags has been set in visit_assignment_expression // and visit_update_expression, this is always a write-only reference here. if !self.current_reference_flags.is_write() { @@ -1819,7 +1821,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_member_expression(it.to_member_expression()); } } - self.current_reference_flags = prev_reference_flags; self.leave_node(kind); } @@ -1828,10 +1829,8 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { it: &AssignmentTargetPropertyIdentifier<'a>, ) { // NOTE: AstKind doesn't exists! - let prev_reference_flags = self.current_reference_flags; self.current_reference_flags = ReferenceFlags::Write; self.visit_identifier_reference(&it.binding); - self.current_reference_flags = prev_reference_flags; if let Some(init) = &it.init { self.visit_expression(init); } @@ -1850,10 +1849,8 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { ExportDefaultDeclarationKind::Identifier(it) => { // `export default ident` // ^^^^^ -> can reference both type/value symbols - let prev_reference_flags = self.current_reference_flags; self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; self.visit_identifier_reference(it); - self.current_reference_flags = prev_reference_flags; } match_expression!(ExportDefaultDeclarationKind) => { self.visit_expression(it.to_expression()); @@ -1868,13 +1865,10 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { if let Some(declaration) = &it.declaration { self.visit_declaration(declaration); } - // let prev_reference_flags = self.current_reference_flags; if it.export_kind.is_type() { self.current_reference_flags = ReferenceFlags::Type; } self.visit_export_specifiers(&it.specifiers); - // self.current_reference_flags = prev_reference_flags; - if let Some(source) = &it.source { self.visit_string_literal(source); } @@ -1914,14 +1908,12 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { let kind = AstKind::TSExportAssignment(self.alloc(it)); self.enter_node(kind); self.visit_span(&it.span); - let prev_reference_flags = self.current_reference_flags; // export = a; // ^ can reference type/value symbols if it.expression.is_identifier_reference() { self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; } self.visit_expression(&it.expression); - self.current_reference_flags = prev_reference_flags; self.leave_node(kind); } } @@ -2140,11 +2132,12 @@ impl<'a> SemanticBuilder<'a> { /// Resolve reference flags for the current ast node. #[inline] - fn resolve_reference_usages(&self) -> ReferenceFlags { + fn resolve_reference_usages(&mut self) -> ReferenceFlags { if self.current_reference_flags.is_empty() { ReferenceFlags::Read } else { - self.current_reference_flags + // Take the current reference flags so that we can reset it to empty + mem::take(&mut self.current_reference_flags) } } } diff --git a/crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.snap b/crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.snap new file mode 100644 index 0000000000000..70dd01211cac4 --- /dev/null +++ b/crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.snap @@ -0,0 +1,50 @@ +--- +source: crates/oxc_semantic/tests/main.rs +input_file: crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.ts +--- +[ + { + "children": [ + { + "children": [], + "flags": "ScopeFlags(StrictMode)", + "id": 1, + "node": "TSInterfaceDeclaration", + "symbols": [] + } + ], + "flags": "ScopeFlags(StrictMode | Top)", + "id": 0, + "node": "Program", + "symbols": [ + { + "flags": "SymbolFlags(Import)", + "id": 0, + "name": "forwardRef", + "node": "ImportSpecifier(forwardRef)", + "references": [ + { + "flags": "ReferenceFlags(Read)", + "id": 1, + "name": "forwardRef", + "node_id": 19 + } + ] + }, + { + "flags": "SymbolFlags(Interface)", + "id": 1, + "name": "MenuTriggerProps", + "node": "TSInterfaceDeclaration", + "references": [] + }, + { + "flags": "SymbolFlags(BlockScopedVariable | ConstVariable)", + "id": 2, + "name": "MenuTrigger", + "node": "VariableDeclarator(MenuTrigger)", + "references": [] + } + ] + } +] diff --git a/crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.ts b/crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.ts new file mode 100644 index 0000000000000..ec2b65bed8f68 --- /dev/null +++ b/crates/oxc_semantic/tests/fixtures/oxc/ts/exports/named/interface-heritage.ts @@ -0,0 +1,3 @@ +import { forwardRef } from "react"; +export interface MenuTriggerProps extends Object {} +export const MenuTrigger = forwardRef(); diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap index 6a91b4dbeaa23..1a981f99f0e37 100644 --- a/tasks/coverage/snapshots/semantic_typescript.snap +++ b/tasks/coverage/snapshots/semantic_typescript.snap @@ -36818,9 +36818,6 @@ rebuilt : ScopeId(19): ["key", "obj"] Bindings mismatch: after transform: ScopeId(26): ["T", "a"] rebuilt : ScopeId(21): ["a"] -Reference flags mismatch for "Monkey": -after transform: ReferenceId(7): ReferenceFlags(Type) -rebuilt : ReferenceId(2): ReferenceFlags(Read) Unresolved references mismatch: after transform: ["Partial", "Readonly"] rebuilt : [] @@ -40376,9 +40373,6 @@ rebuilt : SymbolId(0): Span { start: 62, end: 63 } Symbol redeclarations mismatch for "B": after transform: SymbolId(1): [Span { start: 62, end: 63 }] rebuilt : SymbolId(0): [] -Reference flags mismatch for "B": -after transform: ReferenceId(1): ReferenceFlags(Type) -rebuilt : ReferenceId(0): ReferenceFlags(Read) tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/accessorsOverrideProperty8.ts semantic error: Bindings mismatch: From b8d2bd2eba4d575b694fbe3aad681f556e0c73b1 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:26:53 +0000 Subject: [PATCH 05/21] refactor(semantic): move determining references flags for export specifier to `visit_export_named_declaration` (#7924) ``` ts export type { a }; export { type b }; ``` In the above cases, `a` and `b` are both type-only references. Before, we handled them in `visit_export_named_declaration` and `visit_export_specifier`, but it doesn't look intuitive due to we need to determine if `ExportNamedSpecifier` is a type-only in `visit_export_specifier ` by checking if `current_reference_flags` is a type, and also needs to set it back before exit node. This PR moves determining reference flags from `visit_export_specifier` to `visit_export_named_declaration` so that we don't need to check `current_reference_flags` and set it back because here we can always know the if `ExportSpecifierNamed` is type-only by `ExportNamedDeclaration::export_kind::is_type`. --- crates/oxc_semantic/src/builder.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index e6ece6045ed9d..e2af5fd23b8b3 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -1865,10 +1865,20 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { if let Some(declaration) = &it.declaration { self.visit_declaration(declaration); } - if it.export_kind.is_type() { - self.current_reference_flags = ReferenceFlags::Type; + + for specifier in &it.specifiers { + // `export type { a }` or `export { type a }` -> `a` is a type reference + if it.export_kind.is_type() || specifier.export_kind.is_type() { + self.current_reference_flags = ReferenceFlags::Type; + } else { + // If the export specifier is not a explicit type export, we consider it as a potential + // type and value reference. If it references to a value in the end, we would delete the + // `ReferenceFlags::Type` flag in `fn resolve_references_for_current_scope`. + self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; + } + self.visit_export_specifier(specifier); } - self.visit_export_specifiers(&it.specifiers); + if let Some(source) = &it.source { self.visit_string_literal(source); } @@ -1884,21 +1894,10 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_span(&it.span); self.current_node_flags |= NodeFlags::ExportSpecifier; - let prev_reference_flags = self.current_reference_flags; - // `export type { a }` or `export { type a }` -> `a` is a type reference - if prev_reference_flags.is_type() || it.export_kind.is_type() { - self.current_reference_flags = ReferenceFlags::Type; - } else { - // If the export specifier is not a explicit type export, we consider it as a potential - // type and value reference. If it references to a value in the end, we would delete the - // `ReferenceFlags::Type` flag in `fn resolve_references_for_current_scope`. - self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; - } self.visit_module_export_name(&it.local); self.visit_module_export_name(&it.exported); - self.current_reference_flags = prev_reference_flags; self.current_node_flags -= NodeFlags::ExportSpecifier; self.leave_node(kind); From d94923d3b29bc403f68b7647835ec3b4bdec7bc9 Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Mon, 16 Dec 2024 05:20:17 +0000 Subject: [PATCH 06/21] fix(tools): `just submodules` update transformer test fixtures (#7893) `just submodules` run `just update-transformer-fixtures`. Otherwise after updating submodules, transformer tests will break. Most people won't know that they need to run `just update-transformer-fixtures` too. Also make `just update-transformer-fixtures` work on Windows. --- justfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index 7e592ef2367e4..97aa0defe6863 100755 --- a/justfile +++ b/justfile @@ -40,6 +40,7 @@ submodules: just clone-submodule tasks/coverage/babel https://github.com/babel/babel.git 54a8389fa31ce4fd18b0335b05832dc1ad3cc21f just clone-submodule tasks/coverage/typescript https://github.com/microsoft/TypeScript.git d85767abfd83880cea17cea70f9913e9c4496dcc just clone-submodule tasks/prettier_conformance/prettier https://github.com/prettier/prettier.git 37fd1774d13ef68abcc03775ceef0a91f87a57d7 + just update-transformer-fixtures # Install git pre-commit to format files install-hook: @@ -133,10 +134,9 @@ autoinherit: test-transform *args='': cargo run -p oxc_transform_conformance -- --exec {{args}} -# Update transformer conformance test fixtures, including overrides. -# `just submodules` also does this, but this runs faster. Useful when working on transformer. +# Update transformer conformance test fixtures update-transformer-fixtures: - cd tasks/coverage/babel && git reset --hard HEAD && git clean -f -q + cd tasks/coverage/babel; git reset --hard HEAD; git clean -f -q node tasks/transform_conformance/update_fixtures.mjs # Install wasm-pack From 850dd435978c11c05cd5b4115fe21359d207caec Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Mon, 16 Dec 2024 05:35:05 +0000 Subject: [PATCH 07/21] fix(codegen): missing `,` when generating type parameters with jsx (#7929) close: #7917 --- crates/oxc_codegen/src/gen.rs | 6 ++++++ crates/oxc_codegen/src/lib.rs | 4 ++++ .../oxc_codegen/tests/integration/tester.rs | 19 ++++++++++++++++++- crates/oxc_codegen/tests/integration/ts.rs | 8 +++++++- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index e216ea69f6f06..2c4c766ce0ec4 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -38,6 +38,8 @@ pub trait GenExpr: GetSpan { impl Gen for Program<'_> { fn gen(&self, p: &mut Codegen, ctx: Context) { + p.is_jsx = self.source_type.is_jsx(); + if let Some(hashbang) = &self.hashbang { hashbang.print(p, ctx); } @@ -2976,6 +2978,10 @@ impl Gen for TSTypeParameterDeclaration<'_> { p.print_soft_newline(); p.dedent(); p.print_indent(); + } else if p.is_jsx { + // `() => {}` + // ^ We need a comma here, otherwise it will be regarded as a JSX element. + p.print_str(","); } p.print_ascii_byte(b'>'); } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index b47218d452167..f9c8e9e609821 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -89,6 +89,9 @@ pub struct Codegen<'a> { need_space_before_dot: usize, print_next_indent_as_space: bool, binary_expr_stack: Vec>, + /// Indicates the output is JSX type, it is set in [`Program::gen`] and the result + /// is obtained by [`oxc_span::SourceType::is_jsx`] + is_jsx: bool, /// For avoiding `;` if the previous statement ends with `}`. needs_semicolon: bool, @@ -170,6 +173,7 @@ impl<'a> Codegen<'a> { start_of_stmt: 0, start_of_arrow_expr: 0, start_of_default_export: 0, + is_jsx: false, indent: 0, quote: b'"', print_comments, diff --git a/crates/oxc_codegen/tests/integration/tester.rs b/crates/oxc_codegen/tests/integration/tester.rs index c8955cab6aea5..69f89835feadb 100644 --- a/crates/oxc_codegen/tests/integration/tester.rs +++ b/crates/oxc_codegen/tests/integration/tester.rs @@ -8,7 +8,24 @@ pub fn test(source_text: &str, expected: &str) { } pub fn test_options(source_text: &str, expected: &str, options: CodegenOptions) { - let source_type = SourceType::jsx(); + test_options_with_source_type(source_text, expected, SourceType::jsx(), options); +} + +pub fn test_tsx(source_text: &str, expected: &str) { + test_options_with_source_type( + source_text, + expected, + SourceType::tsx(), + CodegenOptions::default(), + ); +} + +pub fn test_options_with_source_type( + source_text: &str, + expected: &str, + source_type: SourceType, + options: CodegenOptions, +) { let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let result = CodeGenerator::new().with_options(options).build(&ret.program).code; diff --git a/crates/oxc_codegen/tests/integration/ts.rs b/crates/oxc_codegen/tests/integration/ts.rs index ca06641ef4ffb..1d9daa5857c72 100644 --- a/crates/oxc_codegen/tests/integration/ts.rs +++ b/crates/oxc_codegen/tests/integration/ts.rs @@ -1,6 +1,6 @@ use oxc_codegen::CodegenOptions; -use crate::{snapshot, snapshot_options}; +use crate::{snapshot, snapshot_options, tester::test_tsx}; #[test] fn ts() { @@ -99,3 +99,9 @@ export { default as name16 } from "module-name"; &CodegenOptions { minify: true, ..CodegenOptions::default() }, ); } + +#[test] +fn tsx() { + test_tsx("() => {}", "() => {};\n"); + test_tsx("() => {}", "<\n\tT,\n\tB\n>() => {};\n"); +} From dcb27ff6cd0b18106be4ab70232f62ed248dc2b3 Mon Sep 17 00:00:00 2001 From: Boshen Date: Mon, 16 Dec 2024 13:52:15 +0800 Subject: [PATCH 08/21] ci: change setup-zig (#7930) --- .github/workflows/release_napi_parser.yml | 2 +- .github/workflows/release_napi_transform.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_napi_parser.yml b/.github/workflows/release_napi_parser.yml index 8ebbbe20da349..e6b2912f38137 100644 --- a/.github/workflows/release_napi_parser.yml +++ b/.github/workflows/release_napi_parser.yml @@ -92,7 +92,7 @@ jobs: ### install musl dependencies ### # - - uses: goto-bus-stop/setup-zig@222d316fd35ce56a4141fe4a869e1aeefc7f3590 # v1.4.0 + - uses: mlugg/setup-zig@a67e68dc5c8281d9608136d3d7ca1b282213e4ac # v1.2.1 if: ${{ contains(matrix.target, 'musl') }} with: version: 0.11.0 diff --git a/.github/workflows/release_napi_transform.yml b/.github/workflows/release_napi_transform.yml index b694ceb7c83e1..9622f789f5681 100644 --- a/.github/workflows/release_napi_transform.yml +++ b/.github/workflows/release_napi_transform.yml @@ -92,7 +92,7 @@ jobs: ### install musl dependencies ### - - uses: goto-bus-stop/setup-zig@222d316fd35ce56a4141fe4a869e1aeefc7f3590 # v1.4.0 + - uses: mlugg/setup-zig@a67e68dc5c8281d9608136d3d7ca1b282213e4ac # v1.2.1 if: ${{ contains(matrix.target, 'musl') }} with: version: 0.11.0 From 14c51ffa1ddd338559a3f7bc6dd8afb74d6556ca Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:26:11 +0000 Subject: [PATCH 09/21] fix(semantic): remove inherting `ScopeFlags::Modifier` from parent scope (#7932) close: #7900 After #4283 changed, we don't need to inherit `ScopeFlags` from the `constructor`, `set`, `get` anymore, I think this is a logic of forgetting to remove --- .../src/rules/eslint/no_setter_return.rs | 15 +++++++++++-- crates/oxc_semantic/src/scope.rs | 16 ++------------ .../snapshots/semantic_typescript.snap | 4 ++-- .../snapshots/oxc.snap.md | 21 ++----------------- 4 files changed, 19 insertions(+), 37 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_setter_return.rs b/crates/oxc_linter/src/rules/eslint/no_setter_return.rs index 5ca39578815ae..ecbb577af46d0 100644 --- a/crates/oxc_linter/src/rules/eslint/no_setter_return.rs +++ b/crates/oxc_linter/src/rules/eslint/no_setter_return.rs @@ -41,8 +41,19 @@ impl Rule for NoSetterReturn { let AstKind::ReturnStatement(stmt) = node.kind() else { return; }; - if stmt.argument.is_some() && ctx.scopes().get_flags(node.scope_id()).is_set_accessor() { - ctx.diagnostic(no_setter_return_diagnostic(stmt.span)); + if stmt.argument.is_none() { + return; + } + + for scope_id in ctx.scopes().ancestors(node.scope_id()) { + let flags = ctx.scopes().get_flags(scope_id); + if flags.is_set_accessor() { + ctx.diagnostic(no_setter_return_diagnostic(stmt.span)); + } else if flags.is_function() { + break; + } else { + continue; + } } } } diff --git a/crates/oxc_semantic/src/scope.rs b/crates/oxc_semantic/src/scope.rs index 5fa12b0c901db..aada47ae49786 100644 --- a/crates/oxc_semantic/src/scope.rs +++ b/crates/oxc_semantic/src/scope.rs @@ -143,21 +143,9 @@ impl ScopeTree { } /// Get [`ScopeFlags`] for a new child scope under `parent_scope_id`. - pub fn get_new_scope_flags( - &self, - mut flags: ScopeFlags, - parent_scope_id: ScopeId, - ) -> ScopeFlags { + pub fn get_new_scope_flags(&self, flags: ScopeFlags, parent_scope_id: ScopeId) -> ScopeFlags { // https://tc39.es/ecma262/#sec-strict-mode-code - let parent_scope_flags = self.get_flags(parent_scope_id); - flags |= parent_scope_flags & ScopeFlags::StrictMode; - - // inherit flags for non-function scopes - if !flags.contains(ScopeFlags::Function) { - flags |= parent_scope_flags & ScopeFlags::Modifiers; - } - - flags + flags | self.get_flags(parent_scope_id) & ScopeFlags::StrictMode } #[inline] diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap index 1a981f99f0e37..d09a00ddc71c5 100644 --- a/tasks/coverage/snapshots/semantic_typescript.snap +++ b/tasks/coverage/snapshots/semantic_typescript.snap @@ -54767,7 +54767,7 @@ Bindings mismatch: after transform: ScopeId(32): ["A", "B", "C", "E"] rebuilt : ScopeId(24): ["E"] Scope flags mismatch: -after transform: ScopeId(32): ScopeFlags(StrictMode | Constructor) +after transform: ScopeId(32): ScopeFlags(StrictMode) rebuilt : ScopeId(24): ScopeFlags(StrictMode | Function) Bindings mismatch: after transform: ScopeId(34): ["C", "E"] @@ -54785,7 +54785,7 @@ Bindings mismatch: after transform: ScopeId(38): ["A", "B", "C", "E"] rebuilt : ScopeId(30): ["E"] Scope flags mismatch: -after transform: ScopeId(38): ScopeFlags(StrictMode | GetAccessor) +after transform: ScopeId(38): ScopeFlags(StrictMode) rebuilt : ScopeId(30): ScopeFlags(StrictMode | Function) Symbol flags mismatch for "E": after transform: SymbolId(1): SymbolFlags(RegularEnum) diff --git a/tasks/transform_conformance/snapshots/oxc.snap.md b/tasks/transform_conformance/snapshots/oxc.snap.md index cb6bf018b4c40..19612c36acd99 100644 --- a/tasks/transform_conformance/snapshots/oxc.snap.md +++ b/tasks/transform_conformance/snapshots/oxc.snap.md @@ -1,6 +1,6 @@ commit: 54a8389f -Passed: 110/125 +Passed: 111/125 # All Passed: * babel-plugin-transform-class-static-block @@ -16,24 +16,7 @@ Passed: 110/125 * regexp -# babel-plugin-transform-class-properties (11/14) -* instance-prop-initializer-no-existing-constructor/input.js -Scope flags mismatch: -after transform: ScopeId(12): ScopeFlags(StrictMode) -rebuilt : ScopeId(13): ScopeFlags(StrictMode | Constructor) -Scope flags mismatch: -after transform: ScopeId(13): ScopeFlags(StrictMode) -rebuilt : ScopeId(14): ScopeFlags(StrictMode | Constructor) -Scope flags mismatch: -after transform: ScopeId(14): ScopeFlags(StrictMode) -rebuilt : ScopeId(15): ScopeFlags(StrictMode | Constructor) -Scope flags mismatch: -after transform: ScopeId(15): ScopeFlags(StrictMode) -rebuilt : ScopeId(16): ScopeFlags(StrictMode | Constructor) -Scope flags mismatch: -after transform: ScopeId(20): ScopeFlags(StrictMode) -rebuilt : ScopeId(21): ScopeFlags(StrictMode | Constructor) - +# babel-plugin-transform-class-properties (12/14) * typescript/optional-call/input.ts Symbol reference IDs mismatch for "X": after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(11), ReferenceId(16)] From 84b75a0e5f29ddcecf67c18babc5574a75606f8b Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:15:12 +0000 Subject: [PATCH 10/21] refactor(semantic)!: remove `ScopeFlags::Modifiers` (#7935) #7932 removed the only usage of `ScopeFlags::Modifiers`. So we don't need it any more. Remove it. --- crates/oxc_syntax/src/scope.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/oxc_syntax/src/scope.rs b/crates/oxc_syntax/src/scope.rs index f293637df0204..442f26011b3e5 100644 --- a/crates/oxc_syntax/src/scope.rs +++ b/crates/oxc_syntax/src/scope.rs @@ -74,7 +74,6 @@ bitflags! { const SetAccessor = 1 << 8; const CatchClause = 1 << 9; const Var = Self::Top.bits() | Self::Function.bits() | Self::ClassStaticBlock.bits() | Self::TsModuleBlock.bits(); - const Modifiers = Self::Constructor.bits() | Self::GetAccessor.bits() | Self::SetAccessor.bits(); } } From 9b3a2beaa30a87d39a7fbad9278adad446e18b21 Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:08:11 +0000 Subject: [PATCH 11/21] refactor(benchmark): transform code before codegen (#7934) @overlookmotel suggested this [here](https://github.com/oxc-project/oxc/pull/7926#discussion_r1886023496). --- tasks/benchmark/Cargo.toml | 10 +++++++++- tasks/benchmark/benches/codegen.rs | 29 +++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/tasks/benchmark/Cargo.toml b/tasks/benchmark/Cargo.toml index 2046bf6ceae61..7f33c7a6aa3ed 100644 --- a/tasks/benchmark/Cargo.toml +++ b/tasks/benchmark/Cargo.toml @@ -120,7 +120,15 @@ minifier = [ "dep:oxc_span", "dep:oxc_tasks_common", ] -codegen = ["dep:oxc_allocator", "dep:oxc_codegen", "dep:oxc_parser", "dep:oxc_span", "dep:oxc_tasks_common"] +codegen = [ + "dep:oxc_allocator", + "dep:oxc_codegen", + "dep:oxc_parser", + "dep:oxc_semantic", + "dep:oxc_span", + "dep:oxc_tasks_common", + "dep:oxc_transformer", +] linter = [ "dep:oxc_allocator", "dep:oxc_linter", diff --git a/tasks/benchmark/benches/codegen.rs b/tasks/benchmark/benches/codegen.rs index c2e327e2c008f..b4bf1f379f96e 100644 --- a/tasks/benchmark/benches/codegen.rs +++ b/tasks/benchmark/benches/codegen.rs @@ -1,11 +1,13 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use oxc_allocator::Allocator; use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion}; use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_parser::Parser; +use oxc_semantic::SemanticBuilder; use oxc_span::SourceType; use oxc_tasks_common::TestFiles; +use oxc_transformer::{TransformOptions, Transformer}; fn bench_codegen(criterion: &mut Criterion) { for file in TestFiles::complicated_one(0).files() { @@ -13,16 +15,35 @@ fn bench_codegen(criterion: &mut Criterion) { let source_type = SourceType::from_path(&file.file_name).unwrap(); let allocator = Allocator::default(); let source_text = &file.source_text; - let ret = Parser::new(&allocator, source_text, source_type).parse(); + + let mut parser_ret = Parser::new(&allocator, source_text, source_type).parse(); + assert!(parser_ret.errors.is_empty()); let mut group = criterion.benchmark_group("codegen"); - group.bench_with_input(id.clone(), &ret.program, |b, program| { + group.bench_with_input(id.clone(), &parser_ret.program, |b, program| { b.iter_with_large_drop(|| CodeGenerator::new().build(program).map); }); group.finish(); + let transformed_program = { + let transform_options = TransformOptions::enable_all(); + let (symbols, scopes) = SemanticBuilder::new() + // Estimate transformer will triple scopes, symbols, references + .with_excess_capacity(2.0) + .build(&parser_ret.program) + .semantic + .into_symbol_table_and_scope_tree(); + + let transformer_ret = + Transformer::new(&allocator, Path::new(&file.file_name), &transform_options) + .build_with_symbols_and_scopes(symbols, scopes, &mut parser_ret.program); + + assert!(transformer_ret.errors.is_empty()); + parser_ret.program + }; + let mut group = criterion.benchmark_group("codegen_sourcemap"); - group.bench_with_input(id, &ret.program, |b, program| { + group.bench_with_input(id, &transformed_program, |b, program| { b.iter_with_large_drop(|| { CodeGenerator::new() .with_options(CodegenOptions { From 4799471c1ec3c6d60e9abe9f97d9319481b06d72 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:25:39 +0000 Subject: [PATCH 12/21] fix(minfier): bigint bitwise operation only works with bigint (#7937) --- .../src/constant_evaluation/mod.rs | 24 ++++++++++--------- .../src/ast_passes/peephole_fold_constants.rs | 5 ++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs index 92cb7de23ed86..a368262908466 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs @@ -318,6 +318,19 @@ pub trait ConstantEvaluation<'a> { }) } BinaryOperator::BitwiseAnd | BinaryOperator::BitwiseOR | BinaryOperator::BitwiseXOR => { + if left.is_big_int_literal() && right.is_big_int_literal() { + let left_bigint = self.get_side_free_bigint_value(left); + let right_bigint = self.get_side_free_bigint_value(right); + if let (Some(left_val), Some(right_val)) = (left_bigint, right_bigint) { + let result_val: BigInt = match operator { + BinaryOperator::BitwiseAnd => left_val & right_val, + BinaryOperator::BitwiseOR => left_val | right_val, + BinaryOperator::BitwiseXOR => left_val ^ right_val, + _ => unreachable!(), + }; + return Some(ConstantValue::BigInt(result_val)); + } + } let left_num = self.get_side_free_number_value(left); let right_num = self.get_side_free_number_value(right); if let (Some(left_val), Some(right_val)) = (left_num, right_num) { @@ -332,17 +345,6 @@ pub trait ConstantEvaluation<'a> { }; return Some(ConstantValue::Number(result_val)); } - let left_bitint = self.get_side_free_bigint_value(left); - let right_bitint = self.get_side_free_bigint_value(right); - if let (Some(left_val), Some(right_val)) = (left_bitint, right_bitint) { - let result_val: BigInt = match operator { - BinaryOperator::BitwiseAnd => left_val & right_val, - BinaryOperator::BitwiseOR => left_val | right_val, - BinaryOperator::BitwiseXOR => left_val ^ right_val, - _ => unreachable!(), - }; - return Some(ConstantValue::BigInt(result_val)); - } None } _ => None, diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 4adce50a44db2..9918be606dc18 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -1264,6 +1264,11 @@ mod test { test("x = 3n ^ y ^ 1n", "x = y ^ 2n"); test("x = y ^ 3n ^ 3n", "x = y ^ 0n"); test("x = 3n ^ y ^ 3n", "x = y ^ 0n"); + + // TypeError: Cannot mix BigInt and other types + test_same("1n & 1"); + test_same("1n | 1"); + test_same("1n ^ 1"); } #[test] From 3c73e86f8cc93433c4fa38ff23b49e4611576c80 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:51:39 +0000 Subject: [PATCH 13/21] ci(codegen): simplify codegen benchmark (#7938) Follow-on after #7934. Pure refactor. Make codegen benchmark simpler. --- tasks/benchmark/benches/codegen.rs | 35 +++++++++++++----------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/tasks/benchmark/benches/codegen.rs b/tasks/benchmark/benches/codegen.rs index b4bf1f379f96e..d761ec33736ff 100644 --- a/tasks/benchmark/benches/codegen.rs +++ b/tasks/benchmark/benches/codegen.rs @@ -16,41 +16,36 @@ fn bench_codegen(criterion: &mut Criterion) { let allocator = Allocator::default(); let source_text = &file.source_text; - let mut parser_ret = Parser::new(&allocator, source_text, source_type).parse(); + // Codegen + let parser_ret = Parser::new(&allocator, source_text, source_type).parse(); assert!(parser_ret.errors.is_empty()); + let mut program = parser_ret.program; let mut group = criterion.benchmark_group("codegen"); - group.bench_with_input(id.clone(), &parser_ret.program, |b, program| { - b.iter_with_large_drop(|| CodeGenerator::new().build(program).map); + group.bench_function(id.clone(), |b| { + b.iter_with_large_drop(|| CodeGenerator::new().build(&program).map); }); group.finish(); - let transformed_program = { - let transform_options = TransformOptions::enable_all(); - let (symbols, scopes) = SemanticBuilder::new() - // Estimate transformer will triple scopes, symbols, references - .with_excess_capacity(2.0) - .build(&parser_ret.program) - .semantic - .into_symbol_table_and_scope_tree(); + // Codegen sourcemap + let (symbols, scopes) = + SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); - let transformer_ret = - Transformer::new(&allocator, Path::new(&file.file_name), &transform_options) - .build_with_symbols_and_scopes(symbols, scopes, &mut parser_ret.program); - - assert!(transformer_ret.errors.is_empty()); - parser_ret.program - }; + let transform_options = TransformOptions::enable_all(); + let transformer_ret = + Transformer::new(&allocator, Path::new(&file.file_name), &transform_options) + .build_with_symbols_and_scopes(symbols, scopes, &mut program); + assert!(transformer_ret.errors.is_empty()); let mut group = criterion.benchmark_group("codegen_sourcemap"); - group.bench_with_input(id, &transformed_program, |b, program| { + group.bench_function(id, |b| { b.iter_with_large_drop(|| { CodeGenerator::new() .with_options(CodegenOptions { source_map_path: Some(PathBuf::from(&file.file_name)), ..CodegenOptions::default() }) - .build(program) + .build(&program) }); }); group.finish(); From 6f8bb1cebf817c8b0bfab4be954a9023643ec2d1 Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Mon, 16 Dec 2024 14:01:01 +0000 Subject: [PATCH 14/21] ci(benchmarks): simplify benchmarks (#7939) Pure refactor. Use `bench_function` instead of `bench_with_input` and just borrow data from outside closure. This shortens the code and (I think) makes it easier to read. --- .../benches/isolated_declarations.rs | 3 +- tasks/benchmark/benches/lexer.rs | 28 +++++++------- tasks/benchmark/benches/linter.rs | 37 +++++++++---------- tasks/benchmark/benches/parser.rs | 36 +++++++++--------- tasks/benchmark/benches/prettier.rs | 23 +++++------- tasks/benchmark/benches/semantic.rs | 32 ++++++++-------- 6 files changed, 74 insertions(+), 85 deletions(-) diff --git a/tasks/benchmark/benches/isolated_declarations.rs b/tasks/benchmark/benches/isolated_declarations.rs index 91d02286fc43c..6f638b9877b60 100644 --- a/tasks/benchmark/benches/isolated_declarations.rs +++ b/tasks/benchmark/benches/isolated_declarations.rs @@ -13,9 +13,10 @@ fn bench_isolated_declarations(criterion: &mut Criterion) { ); let id = BenchmarkId::from_parameter(&file.file_name); + let source_text = file.source_text.as_str(); let source_type = SourceType::from_path(&file.file_name).unwrap(); - group.bench_with_input(id, &file.source_text, |b, source_text| { + group.bench_function(id, |b| { b.iter_with_large_drop(|| { let allocator = Allocator::default(); let ParserReturn { program, .. } = diff --git a/tasks/benchmark/benches/lexer.rs b/tasks/benchmark/benches/lexer.rs index e0aebdefdcf17..234da608bf81f 100644 --- a/tasks/benchmark/benches/lexer.rs +++ b/tasks/benchmark/benches/lexer.rs @@ -23,22 +23,20 @@ fn bench_lexer(criterion: &mut Criterion) { .collect::>(); for file in files { + let id = BenchmarkId::from_parameter(&file.file_name); + let source_text = file.source_text.as_str(); let source_type = SourceType::from_path(&file.file_name).unwrap(); - group.bench_with_input( - BenchmarkId::from_parameter(&file.file_name), - &file.source_text, - |b, source_text| { - // Do not include initializing allocator in benchmark. - // User code would likely reuse the same allocator over and over to parse multiple files, - // so we do the same here. - let mut allocator = Allocator::default(); - b.iter(|| { - let mut lexer = Lexer::new_for_benchmarks(&allocator, source_text, source_type); - while lexer.next_token().kind != Kind::Eof {} - allocator.reset(); - }); - }, - ); + group.bench_function(id, |b| { + // Do not include initializing allocator in benchmark. + // User code would likely reuse the same allocator over and over to parse multiple files, + // so we do the same here. + let mut allocator = Allocator::default(); + b.iter(|| { + let mut lexer = Lexer::new_for_benchmarks(&allocator, source_text, source_type); + while lexer.next_token().kind != Kind::Eof {} + allocator.reset(); + }); + }); } group.finish(); } diff --git a/tasks/benchmark/benches/linter.rs b/tasks/benchmark/benches/linter.rs index 9b51eef628f36..d98ac22a5514a 100644 --- a/tasks/benchmark/benches/linter.rs +++ b/tasks/benchmark/benches/linter.rs @@ -24,27 +24,24 @@ fn bench_linter(criterion: &mut Criterion) { } for file in test_files { + let id = BenchmarkId::from_parameter(&file.file_name); + let source_text = file.source_text.as_str(); let source_type = SourceType::from_path(&file.file_name).unwrap(); - group.bench_with_input( - BenchmarkId::from_parameter(&file.file_name), - &file.source_text, - |b, source_text| { - let allocator = Allocator::default(); - let ret = Parser::new(&allocator, source_text, source_type).parse(); - let path = Path::new(""); - let semantic_ret = SemanticBuilder::new() - .with_build_jsdoc(true) - .with_scope_tree_child_ids(true) - .with_cfg(true) - .build(&ret.program); - let semantic = semantic_ret.semantic; - let module_record = - Arc::new(ModuleRecord::new(path, &ret.module_record, &semantic)); - let semantic = Rc::new(semantic); - let linter = LinterBuilder::all().with_fix(FixKind::All).build(); - b.iter(|| linter.run(path, Rc::clone(&semantic), Arc::clone(&module_record))); - }, - ); + group.bench_function(id, |b| { + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, source_text, source_type).parse(); + let path = Path::new(""); + let semantic_ret = SemanticBuilder::new() + .with_build_jsdoc(true) + .with_scope_tree_child_ids(true) + .with_cfg(true) + .build(&ret.program); + let semantic = semantic_ret.semantic; + let module_record = Arc::new(ModuleRecord::new(path, &ret.module_record, &semantic)); + let semantic = Rc::new(semantic); + let linter = LinterBuilder::all().with_fix(FixKind::All).build(); + b.iter(|| linter.run(path, Rc::clone(&semantic), Arc::clone(&module_record))); + }); } group.finish(); } diff --git a/tasks/benchmark/benches/parser.rs b/tasks/benchmark/benches/parser.rs index f0f013c4c589b..5ede362633e83 100644 --- a/tasks/benchmark/benches/parser.rs +++ b/tasks/benchmark/benches/parser.rs @@ -7,26 +7,24 @@ use oxc_tasks_common::TestFiles; fn bench_parser(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("parser"); for file in TestFiles::complicated().files() { + let id = BenchmarkId::from_parameter(&file.file_name); + let source_text = file.source_text.as_str(); let source_type = SourceType::from_path(&file.file_name).unwrap(); - group.bench_with_input( - BenchmarkId::from_parameter(&file.file_name), - &file.source_text, - |b, source_text| { - // Do not include initializing allocator in benchmark. - // User code would likely reuse the same allocator over and over to parse multiple files, - // so we do the same here. - let mut allocator = Allocator::default(); - b.iter(|| { - Parser::new(&allocator, source_text, source_type) - .with_options(ParseOptions { - parse_regular_expression: true, - ..ParseOptions::default() - }) - .parse(); - allocator.reset(); - }); - }, - ); + group.bench_function(id, |b| { + // Do not include initializing allocator in benchmark. + // User code would likely reuse the same allocator over and over to parse multiple files, + // so we do the same here. + let mut allocator = Allocator::default(); + b.iter(|| { + Parser::new(&allocator, source_text, source_type) + .with_options(ParseOptions { + parse_regular_expression: true, + ..ParseOptions::default() + }) + .parse(); + allocator.reset(); + }); + }); } group.finish(); } diff --git a/tasks/benchmark/benches/prettier.rs b/tasks/benchmark/benches/prettier.rs index 0c0961b36f1ea..99ad05811ba7c 100644 --- a/tasks/benchmark/benches/prettier.rs +++ b/tasks/benchmark/benches/prettier.rs @@ -8,20 +8,17 @@ use oxc_tasks_common::TestFiles; fn bench_prettier(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("prettier"); for file in TestFiles::minimal().files() { + let id = BenchmarkId::from_parameter(&file.file_name); + let source_text = file.source_text.as_str(); let source_type = SourceType::from_path(&file.file_name).unwrap(); - group.bench_with_input( - BenchmarkId::from_parameter(&file.file_name), - &file.source_text, - |b, source_text| { - b.iter(|| { - let allocator1 = Allocator::default(); - let allocator2 = Allocator::default(); - let ret = Parser::new(&allocator1, source_text, source_type).parse(); - let _ = - Prettier::new(&allocator2, PrettierOptions::default()).build(&ret.program); - }); - }, - ); + group.bench_function(id, |b| { + b.iter(|| { + let allocator1 = Allocator::default(); + let allocator2 = Allocator::default(); + let ret = Parser::new(&allocator1, source_text, source_type).parse(); + let _ = Prettier::new(&allocator2, PrettierOptions::default()).build(&ret.program); + }); + }); } group.finish(); } diff --git a/tasks/benchmark/benches/semantic.rs b/tasks/benchmark/benches/semantic.rs index 96422cdc9da5a..37583f02469e7 100644 --- a/tasks/benchmark/benches/semantic.rs +++ b/tasks/benchmark/benches/semantic.rs @@ -8,24 +8,22 @@ use oxc_tasks_common::TestFiles; fn bench_semantic(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("semantic"); for file in TestFiles::complicated().files() { + let id = BenchmarkId::from_parameter(&file.file_name); + let source_text = file.source_text.as_str(); let source_type = SourceType::from_path(&file.file_name).unwrap(); - group.bench_with_input( - BenchmarkId::from_parameter(&file.file_name), - &file.source_text, - |b, source_text| { - let allocator = Allocator::default(); - let ret = Parser::new(&allocator, source_text, source_type).parse(); - b.iter_with_large_drop(|| { - // We drop `Semantic` inside this closure as drop time is part of cost of using this API. - // We return `error`s to be dropped outside of the measured section, as usually - // code would have no errors. One of our benchmarks `cal.com.tsx` has a lot of errors, - // but that's atypical, so don't want to include it in benchmark time. - let ret = SemanticBuilder::new().with_build_jsdoc(true).build(&ret.program); - let ret = black_box(ret); - ret.errors - }); - }, - ); + group.bench_function(id, |b| { + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, source_text, source_type).parse(); + b.iter_with_large_drop(|| { + // We drop `Semantic` inside this closure as drop time is part of cost of using this API. + // We return `error`s to be dropped outside of the measured section, as usually + // code would have no errors. One of our benchmarks `cal.com.tsx` has a lot of errors, + // but that's atypical, so don't want to include it in benchmark time. + let ret = SemanticBuilder::new().with_build_jsdoc(true).build(&ret.program); + let ret = black_box(ret); + ret.errors + }); + }); } group.finish(); } From 6f41d929729a7116d651f640a2c72594c92824fb Mon Sep 17 00:00:00 2001 From: dalaoshu Date: Mon, 16 Dec 2024 22:20:58 +0800 Subject: [PATCH 15/21] fix(linter): false positive in `unicorn/no-useless-spread` (#7940) closes #7936 --- .../src/rules/unicorn/no_useless_spread/const_eval.rs | 2 +- crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_spread/const_eval.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_spread/const_eval.rs index 952bab7a9753c..76661b7ad94e4 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_spread/const_eval.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_spread/const_eval.rs @@ -89,7 +89,7 @@ impl ConstEval for Argument<'_> { impl ConstEval for NewExpression<'_> { fn const_eval(&self) -> ValueHint { - if is_new_array(self) || is_new_typed_array(self) { + if is_new_array(self) { ValueHint::NewArray } else if is_new_map_or_set(self) { ValueHint::NewIterable diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs index 3ab38d978f53d..c78b30bcccf91 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs @@ -594,6 +594,9 @@ fn test() { "[...arr.reduce((set, b) => set.add(b), new Set(iter))]", // NOTE: we may want to consider this a violation in the future "[...(foo ? new Set() : [])]", + // Issue: + "[ ...Uint8Array([ 1, 2, 3 ]) ].map(byte => byte.toString())", + "[ ...new Uint8Array([ 1, 2, 3 ]) ].map(byte => byte.toString())", ]; let fail = vec![ From c3c76cb232874377bfe953a0c5afa09c623b279f Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:19:14 +0000 Subject: [PATCH 16/21] chore(deps): update npm packages (#7931) --- editors/vscode/package.json | 4 +- pnpm-lock.yaml | 120 ++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 99e94babc900c..127e7672c0514 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -146,14 +146,14 @@ "devDependencies": { "@types/mocha": "^10.0.9", "@types/node": "^22.0.0", - "@types/vscode": "1.95.0", + "@types/vscode": "1.96.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^3.0.0", "cross-env": "^7.0.3", "esbuild": "^0.24.0", "ovsx": "^0.10.0", - "oxlint": "^0.11.1", + "oxlint": "^0.15.0", "typescript": "^5.4.5" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d05fced3421e7..7841c89fb35e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 5.6.3 vitest: specifier: 'catalog:' - version: 2.1.2(@types/node@22.10.1) + version: 2.1.2(@types/node@22.10.2) editors/vscode: dependencies: @@ -41,10 +41,10 @@ importers: version: 10.0.10 '@types/node': specifier: ^22.0.0 - version: 22.10.1 + version: 22.10.2 '@types/vscode': - specifier: 1.95.0 - version: 1.95.0 + specifier: 1.96.0 + version: 1.96.0 '@vscode/test-cli': specifier: ^0.0.10 version: 0.0.10 @@ -64,8 +64,8 @@ importers: specifier: ^0.10.0 version: 0.10.1 oxlint: - specifier: ^0.11.1 - version: 0.11.1 + specifier: ^0.15.0 + version: 0.15.2 typescript: specifier: ^5.4.5 version: 5.7.2 @@ -1152,43 +1152,43 @@ packages: '@octokit/types@13.6.2': resolution: {integrity: sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==} - '@oxlint/darwin-arm64@0.11.1': - resolution: {integrity: sha512-S+cHn49fT+qSJXhQ3Z4EG/5ENp2dAUbS2sMNkhgkLqlO8aYl0TR9R7omU3vpU/beu8ePnV+mdVlJYGjsPIMGtg==} + '@oxlint/darwin-arm64@0.15.2': + resolution: {integrity: sha512-0Qw3eOArmhNE0B516kbYuy6Bo41WukMoJGUElMYnSXv1lHgghH6dCBN5o3s1YKy665WVHg+Etd46/C865EASqA==} cpu: [arm64] os: [darwin] - '@oxlint/darwin-x64@0.11.1': - resolution: {integrity: sha512-LPuF0D8uu30KIVEeVuGwIPwHwJRQ1i1otwFFH7tRsNXPgMgZJ4VgriyH22i6RWwBtclJoCSBLtGK6gLZ0oZBvw==} + '@oxlint/darwin-x64@0.15.2': + resolution: {integrity: sha512-NTMvRO4yih4rVw5OpwyO1LNFjZqfdgWv64da3+QvGGbvlZCvyMms0nFCyyT1DSyg+7jtcLmyTixKLJ3d4lYY5A==} cpu: [x64] os: [darwin] - '@oxlint/linux-arm64-gnu@0.11.1': - resolution: {integrity: sha512-CYBE+GRIPs5e+raD2pdicuBn6Y6E1xAnyWQ/kHE4GEWDAQZY0Um2VYEUTGH2ObwJ3uXr6jeJ16HOKJvr0S8a8w==} + '@oxlint/linux-arm64-gnu@0.15.2': + resolution: {integrity: sha512-j1U82EVt5vHt0FmGf1Blo7EoLZZS+HNBylxI8axsAU+D7BbnHrN2taqyOQaGTwvpnkOs4OrXuLlCg7BDQX0FuQ==} cpu: [arm64] os: [linux] - '@oxlint/linux-arm64-musl@0.11.1': - resolution: {integrity: sha512-iYXF5N5Gv+lc2wt90kxXy/W0cn7IEWu3UPzewIjPGDH8ajDckvGzZx6pTGYJnTyMh7U6hUKwOBFPVLMWI7UwKQ==} + '@oxlint/linux-arm64-musl@0.15.2': + resolution: {integrity: sha512-MaRktTWeA0HcFEcX9YbgCILuf3qVmBqU4Wctp6QGud0YDnpvrj2SRDhJqPFeAwf0j9W8pZ1UXZWtTalOEVULUw==} cpu: [arm64] os: [linux] - '@oxlint/linux-x64-gnu@0.11.1': - resolution: {integrity: sha512-D0tT8X0CsK/bpdkGdLSmsGftG3VndjyAUJuNGt56JYn0UfuPDkhQcLgUlkANHzNRXJ84tLQKhpf/MUDUHPB5cg==} + '@oxlint/linux-x64-gnu@0.15.2': + resolution: {integrity: sha512-buuORJFuD7xSWR9q71gmnB0ygTRMTURlmi5lvDQz4tdEKkxGI2CX020NaagfKJjZ2JdIHCwdoSe6QsFOw3K9BQ==} cpu: [x64] os: [linux] - '@oxlint/linux-x64-musl@0.11.1': - resolution: {integrity: sha512-WekaLYk8WLT7Di8+nyPvtqs9OlMoO6KjFDMlqqLDWQTk9ffjn8e76PCRigF3w39jQ70qP3c8k8cNKNw5ROuFcg==} + '@oxlint/linux-x64-musl@0.15.2': + resolution: {integrity: sha512-f/OHa5jMfKgvUj5W2gzh3Ehsjwu/EWffUMN7vSrIVmRp20D5igf+B2o1D8LSq64BJAZH9BCvyQZH/xn/leshvQ==} cpu: [x64] os: [linux] - '@oxlint/win32-arm64@0.11.1': - resolution: {integrity: sha512-/CN/bFtI33vB8uemOkZxlNRf6Q7CftP2pSO7a6Q2niG4NC99YRPj7ctXcPF0jGR0NQUhGZk7ajM4G/0MKcRdag==} + '@oxlint/win32-arm64@0.15.2': + resolution: {integrity: sha512-6d5WgybNtIt1f2TWSVVcbhWWfOOV8GyHUBoVCONVI6Iwu/LQayE78o/5Wyx3KVP9JS1tUy8zYZ6JitI3J9BC8g==} cpu: [arm64] os: [win32] - '@oxlint/win32-x64@0.11.1': - resolution: {integrity: sha512-0hLl0z6adYTvLIOPC5uyo+EAwNITkzi4AY4xImykQW8H89GhiV9Xl8MPJeZQMWSz7ajI1I2+hRsvA0QAzeBsxA==} + '@oxlint/win32-x64@0.15.2': + resolution: {integrity: sha512-fQ/IDIiv3XnpnR4/t/BOPkwikCPdyUT26TPTyltPJ/4g/vPJqIB+7gBdO+xys531Y08dW8jZjGjvrhW1BAq5cw==} cpu: [x64] os: [win32] @@ -1301,11 +1301,11 @@ packages: '@types/mute-stream@0.0.4': resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} - '@types/node@22.10.1': - resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} + '@types/node@22.10.2': + resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} - '@types/vscode@1.95.0': - resolution: {integrity: sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==} + '@types/vscode@1.96.0': + resolution: {integrity: sha512-qvZbSZo+K4ZYmmDuaodMbAa67Pl6VDQzLKFka6rq+3WUTY4Kro7Bwoi0CuZLO/wema0ygcmpwow7zZfPJTs5jg==} '@types/wrap-ansi@3.0.0': resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} @@ -2382,8 +2382,8 @@ packages: engines: {node: '>= 20'} hasBin: true - oxlint@0.11.1: - resolution: {integrity: sha512-yVTkBmSvn1mo69vxBdNASOGFd1oqWzpaIWPFPIXNAHxgrW7FjotKuJ71j/pqtZH/sVSRWTpQFdmBa3CIuBEILg==} + oxlint@0.15.2: + resolution: {integrity: sha512-PPXPimwYaFe0G2tbTf1ZYs0dGNGKbLlPHwfnsfcZ0AaOct4RjyOa5NHtDRFpvOMq3EcKZKVUAztzmUDepkxbSQ==} engines: {node: '>=14.*'} hasBin: true @@ -3505,7 +3505,7 @@ snapshots: '@inquirer/figures': 1.0.8 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.10.1 + '@types/node': 22.10.2 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -3920,28 +3920,28 @@ snapshots: dependencies: '@octokit/openapi-types': 22.2.0 - '@oxlint/darwin-arm64@0.11.1': + '@oxlint/darwin-arm64@0.15.2': optional: true - '@oxlint/darwin-x64@0.11.1': + '@oxlint/darwin-x64@0.15.2': optional: true - '@oxlint/linux-arm64-gnu@0.11.1': + '@oxlint/linux-arm64-gnu@0.15.2': optional: true - '@oxlint/linux-arm64-musl@0.11.1': + '@oxlint/linux-arm64-musl@0.15.2': optional: true - '@oxlint/linux-x64-gnu@0.11.1': + '@oxlint/linux-x64-gnu@0.15.2': optional: true - '@oxlint/linux-x64-musl@0.11.1': + '@oxlint/linux-x64-musl@0.15.2': optional: true - '@oxlint/win32-arm64@0.11.1': + '@oxlint/win32-arm64@0.15.2': optional: true - '@oxlint/win32-x64@0.11.1': + '@oxlint/win32-x64@0.15.2': optional: true '@pkgjs/parseargs@0.11.0': @@ -4014,13 +4014,13 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 - '@types/node@22.10.1': + '@types/node@22.10.2': dependencies: undici-types: 6.20.0 - '@types/vscode@1.95.0': {} + '@types/vscode@1.96.0': {} '@types/wrap-ansi@3.0.0': {} @@ -4031,13 +4031,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.4.11(@types/node@22.10.1))': + '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.4.11(@types/node@22.10.2))': dependencies: '@vitest/spy': 2.1.2 estree-walker: 3.0.3 magic-string: 0.30.14 optionalDependencies: - vite: 5.4.11(@types/node@22.10.1) + vite: 5.4.11(@types/node@22.10.2) '@vitest/pretty-format@2.1.2': dependencies: @@ -5239,16 +5239,16 @@ snapshots: - debug - supports-color - oxlint@0.11.1: + oxlint@0.15.2: optionalDependencies: - '@oxlint/darwin-arm64': 0.11.1 - '@oxlint/darwin-x64': 0.11.1 - '@oxlint/linux-arm64-gnu': 0.11.1 - '@oxlint/linux-arm64-musl': 0.11.1 - '@oxlint/linux-x64-gnu': 0.11.1 - '@oxlint/linux-x64-musl': 0.11.1 - '@oxlint/win32-arm64': 0.11.1 - '@oxlint/win32-x64': 0.11.1 + '@oxlint/darwin-arm64': 0.15.2 + '@oxlint/darwin-x64': 0.15.2 + '@oxlint/linux-arm64-gnu': 0.15.2 + '@oxlint/linux-arm64-musl': 0.15.2 + '@oxlint/linux-x64-gnu': 0.15.2 + '@oxlint/linux-x64-musl': 0.15.2 + '@oxlint/win32-arm64': 0.15.2 + '@oxlint/win32-x64': 0.15.2 p-limit@3.1.0: dependencies: @@ -5714,12 +5714,12 @@ snapshots: vary@1.1.2: {} - vite-node@2.1.2(@types/node@22.10.1): + vite-node@2.1.2(@types/node@22.10.2): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@8.1.1) pathe: 1.1.2 - vite: 5.4.11(@types/node@22.10.1) + vite: 5.4.11(@types/node@22.10.2) transitivePeerDependencies: - '@types/node' - less @@ -5731,19 +5731,19 @@ snapshots: - supports-color - terser - vite@5.4.11(@types/node@22.10.1): + vite@5.4.11(@types/node@22.10.2): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.28.0 optionalDependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 fsevents: 2.3.3 - vitest@2.1.2(@types/node@22.10.1): + vitest@2.1.2(@types/node@22.10.2): dependencies: '@vitest/expect': 2.1.2 - '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.4.11(@types/node@22.10.1)) + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.4.11(@types/node@22.10.2)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.2 '@vitest/snapshot': 2.1.2 @@ -5758,11 +5758,11 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@22.10.1) - vite-node: 2.1.2(@types/node@22.10.1) + vite: 5.4.11(@types/node@22.10.2) + vite-node: 2.1.2(@types/node@22.10.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 transitivePeerDependencies: - less - lightningcss From ff2a68f22b3af0e6a5feeb908ac0a8b0309e6568 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:41:16 +0000 Subject: [PATCH 17/21] refactor(linter/yoda): simplify code (#7941) Follow-on after #7679. Simplify `do_diagnostic_with_fix`, in particular the search for the operator. Also reduce `span()` calls, as they have a cost. --- crates/oxc_linter/src/rules/eslint/yoda.rs | 64 ++++++++++------------ 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/yoda.rs b/crates/oxc_linter/src/rules/eslint/yoda.rs index 6f1298efe5722..19efb31b52def 100644 --- a/crates/oxc_linter/src/rules/eslint/yoda.rs +++ b/crates/oxc_linter/src/rules/eslint/yoda.rs @@ -266,61 +266,53 @@ fn is_not_yoda(expr: &BinaryExpression) -> bool { #[allow(clippy::cast_possible_truncation)] fn do_diagnostic_with_fix(expr: &BinaryExpression, ctx: &LintContext, never: bool) { ctx.diagnostic_with_fix(yoda_diagnostic(expr.span, never, expr.operator.as_str()), |fix| { - let flipped_operator = flip_operator(expr.operator); - - let left_str = ctx.source_range(expr.left.span()); - let right_str = ctx.source_range(expr.right.span()); - let flipped_operator_str = flipped_operator.as_str(); + let left_span = expr.left.span(); + let right_span = expr.right.span(); let operator_str = expr.operator.as_str(); - let source_str = ctx.source_range( - Span::new(expr.left.span().end, expr.right.span().start) + let str_between_left_and_right = ctx.source_range( + Span::new(left_span.end, right_span.start) ); - let source_chars = source_str.char_indices().collect::>(); - - let search_start_position = expr.left.span().end; - - let operator_position_start = source_chars.windows(operator_str.len()).find(|str| { - if str.iter().enumerate().all(|(i, (_pos, c))| *c == operator_str.chars().nth(i).unwrap()) { - !ctx.comments().iter().any(|c| { - c.span.start <= (str[0].0 as u32) + search_start_position && (str[0].0 as u32) + operator_str.len() as u32 + search_start_position <= c.span.end - }) - } else { - false - } - }); - - let Some(operator_position_start) = operator_position_start else { - debug_assert!(false); - return fix.noop(); - }; - - let operator_position_start = search_start_position + operator_position_start[0].0 as u32; + let (operator_start, operator_end) = str_between_left_and_right + .as_bytes() + .windows(operator_str.len()) + .enumerate() + .find_map(|(index, chunk)| { + if chunk == operator_str.as_bytes() { + let pos_start = index as u32 + left_span.end; + let pos_end = pos_start + operator_str.len() as u32; + if !ctx.comments().iter().any(|comment| comment.span.start <= pos_start && pos_end <= comment.span.end) { + return Some((pos_start, pos_end)); + } + } + None + }) + .unwrap(); - let operator_position_end = operator_position_start + operator_str.len() as u32; - let str_between_left_and_operator = ctx.source_range(Span::new(expr.left.span().end, operator_position_start)); - let str_between_operator_and_right = ctx.source_range(Span::new(operator_position_end, expr.right.span().start)); + let str_between_left_and_operator = ctx.source_range(Span::new(left_span.end, operator_start)); + let str_between_operator_and_right = ctx.source_range(Span::new(operator_end, right_span.start)); - let left_start = expr.left.span().start; - let left_prev_token = if left_start > 0 && (expr.right.is_literal() || expr.right.is_identifier_reference() ) { - let tokens = ctx.source_range(Span::new(0, left_start)); + let left_prev_token = if left_span.start > 0 && (expr.right.is_literal() || expr.right.is_identifier_reference() ) { + let tokens = ctx.source_range(Span::new(0, left_span.start)); let token = tokens.chars().last(); match_token(token) } else { false }; - let right_end = expr.right.span().end; let source_size = u32::try_from(ctx.source_text().len()).unwrap(); - let right_next_token = if right_end < source_size && (expr.left.is_literal() || expr.left.is_identifier_reference()) { - let tokens = ctx.source_range(Span::new(right_end, source_size)); + let right_next_token = if right_span.end < source_size && (expr.left.is_literal() || expr.left.is_identifier_reference()) { + let tokens = ctx.source_range(Span::new(right_span.end, source_size)); let token = tokens.chars().next(); match_token(token) } else { false }; + let left_str = ctx.source_range(left_span); + let right_str = ctx.source_range(right_span); + let flipped_operator_str = flip_operator(expr.operator).as_str(); let replacement = format!( "{}{right_str}{str_between_left_and_operator}{flipped_operator_str}{str_between_operator_and_right}{left_str}{}", if left_prev_token { " " } else { "" }, From 46e2e27735ca70f4ce0235165ab51183839a0014 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:02:52 +0000 Subject: [PATCH 18/21] feat(data_structures): implement `Default` for `NonEmptyStack` (#7946) Implement `Default` for `NonEmptyStack` where `T: Default`. --- crates/oxc_data_structures/src/stack/non_empty.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/oxc_data_structures/src/stack/non_empty.rs b/crates/oxc_data_structures/src/stack/non_empty.rs index 5e80fa53725ba..0f5bee8ae927a 100644 --- a/crates/oxc_data_structures/src/stack/non_empty.rs +++ b/crates/oxc_data_structures/src/stack/non_empty.rs @@ -366,6 +366,12 @@ impl DerefMut for NonEmptyStack { } } +impl Default for NonEmptyStack { + fn default() -> Self { + Self::new(T::default()) + } +} + #[cfg(test)] mod tests { use super::*; From b961d5477ab05805e36e4f04f9c87315d612cfbd Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Tue, 17 Dec 2024 03:25:11 +0100 Subject: [PATCH 19/21] docs(tasks/website): add legend for fixable column (#7945) --- tasks/website/src/linter/rules/table.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tasks/website/src/linter/rules/table.rs b/tasks/website/src/linter/rules/table.rs index e0c2781603ac6..fc3cf40052f4c 100644 --- a/tasks/website/src/linter/rules/table.rs +++ b/tasks/website/src/linter/rules/table.rs @@ -26,6 +26,13 @@ The progress of all rule implementations is tracked [here](https://github.com/ox - Total number of rules: {total} - Rules turned on by default: {turned_on_by_default_count} +**Legend for 'Fixable?' column:** +- ๐Ÿ› ๏ธ: an auto-fix is available for this rule +- ๐Ÿ’ก: a suggestion is available for this rule +- โš ๏ธ๐Ÿ› ๏ธ: a dangerous auto-fix is available for this rule +- โš ๏ธ๐Ÿ’ก: a dangerous suggestion is available for this rule +- ๐Ÿšง: an auto-fix or suggestion is possible, but currently not implemented + {body} ") } From e0d440a618b2c3309a304b66aa9c3bd25646b7d8 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Tue, 17 Dec 2024 10:32:37 +0800 Subject: [PATCH 20/21] fix(wasm): use `ScopeTree`'s flags rather than `enter_scope`'s flags parameter (#7950) close: https://github.com/oxc-project/oxc/issues/7900#issuecomment-2545374960 --- crates/oxc_wasm/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index a37962b9a08ef..f53839c540bb2 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -378,8 +378,9 @@ impl Oxc { } impl Visit<'_> for ScopesTextWriter<'_> { - fn enter_scope(&mut self, flags: ScopeFlags, scope_id: &Cell>) { + fn enter_scope(&mut self, _: ScopeFlags, scope_id: &Cell>) { let scope_id = scope_id.get().unwrap(); + let flags = self.scopes.get_flags(scope_id); self.write_line(format!("Scope {} ({flags:?}) {{", scope_id.index())); self.indent_in(); From de8a86e3562686cb6f5c1d27bb2c371ffc7f58d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Tue, 17 Dec 2024 11:40:22 +0900 Subject: [PATCH 21/21] fix(minifier): incorrect minification in `try_fold_left_child_op` (#7949) fixes #7944 --- .../src/ast_passes/peephole_fold_constants.rs | 57 +++++++++++++++++++ tasks/minsize/minsize.snap | 12 ++-- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 9918be606dc18..58332ac0e3543 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -258,6 +258,9 @@ impl<'a, 'b> PeepholeFoldConstants { let Expression::BinaryExpression(left) = &mut e.left else { return None; }; + if left.operator != op { + return None; + } let (v, expr_to_move); if let Some(result) = ctx.eval_binary_operation(op, &left.left, &e.right) { @@ -1276,6 +1279,13 @@ mod test { test("x = null & 1", "x = 0"); test("x = (2 ** 31 - 1) | 1", "x = 2147483647"); test("x = (2 ** 31) | 1", "x = -2147483647"); + + // https://github.com/oxc-project/oxc/issues/7944 + test_same("(x - 1) & 1"); + test_same("(y >> 3) & 7"); + test("(y & 3) & 7", "y & 3"); + test_same("(y | 3) & 7"); + test("y | 3 & 7", "y | 3"); } #[test] @@ -1364,6 +1374,7 @@ mod test { test_same("z = x * y"); test_same("x = y * 5"); // test("x = y + (z * 24 * 60 * 60 * 1000)", "x = y + z * 864E5"); + test("x = y + (z & 24 & 60 & 60 & 1000)", "x = y + (z & 8)"); } #[test] @@ -1393,6 +1404,52 @@ mod test { test("x = Infinity % 0", "x = NaN"); } + #[test] + fn test_fold_left() { + test_same("(+x - 1) + 2"); // not yet + test("(+x & 1) & 2", "+x & 0"); + } + + #[test] + fn test_fold_left_child_op() { + test_same("x & infinity & 2"); // FIXME: want x & 0 + test_same("x - infinity - 2"); // FIXME: want "x-infinity" + test_same("x - 1 + infinity"); + test_same("x - 2 + 1"); + test_same("x - 2 + 3"); + test_same("1 + x - 2 + 1"); + test_same("1 + x - 2 + 3"); + test_same("1 + x - 2 + 3 - 1"); + test_same("f(x)-0"); + test_same("x-0-0"); // FIXME: want x - 0 + test_same("x+2-2+2"); + test_same("x+2-2+2-2"); + test_same("x-2+2"); + test_same("x-2+2-2"); + test_same("x-2+2-2+2"); + + test_same("1+x-0-na_n"); + test_same("1+f(x)-0-na_n"); + test_same("1+x-0+na_n"); + test_same("1+f(x)-0+na_n"); + + test_same("1+x+na_n"); // unfoldable + test_same("x+2-2"); // unfoldable + test_same("x+2"); // nothing to do + test_same("x-2"); // nothing to do + } + + #[test] + fn test_associative_fold_constants_with_variables() { + // mul and add should not fold + test_same("alert(x * 12 * 20);"); + test_same("alert(12 * x * 20);"); + test_same("alert(x + 12 + 20);"); + test_same("alert(12 + x + 20);"); + test("alert(x & 12 & 20);", "alert(x & 4);"); + test("alert(12 & x & 20);", "alert(x & 4);"); + } + #[test] fn test_to_number() { test("x = +''", "x = 0"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index fdc3b083f0463..cde7b69638608 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 61.61 kB | 59.82 kB | 19.55 kB | 19.33 kB | moment.js -287.63 kB | 92.60 kB | 90.07 kB | 32.26 kB | 31.95 kB | jquery.js +287.63 kB | 92.61 kB | 90.07 kB | 32.27 kB | 31.95 kB | jquery.js 342.15 kB | 121.79 kB | 118.14 kB | 44.59 kB | 44.37 kB | vue.js 544.10 kB | 73.37 kB | 72.48 kB | 26.13 kB | 26.20 kB | lodash.js -555.77 kB | 276.15 kB | 270.13 kB | 91.13 kB | 90.80 kB | d3.js +555.77 kB | 276.22 kB | 270.13 kB | 91.15 kB | 90.80 kB | d3.js 1.01 MB | 467.14 kB | 458.89 kB | 126.74 kB | 126.71 kB | bundle.min.js -1.25 MB | 662.53 kB | 646.76 kB | 163.97 kB | 163.73 kB | three.js +1.25 MB | 662.68 kB | 646.76 kB | 164.00 kB | 163.73 kB | three.js -2.14 MB | 740.87 kB | 724.14 kB | 181.46 kB | 181.07 kB | victory.js +2.14 MB | 740.94 kB | 724.14 kB | 181.49 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.10 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 332.09 kB | 331.56 kB | echarts.js 6.69 MB | 2.39 MB | 2.31 MB | 496.17 kB | 488.28 kB | antd.js -10.95 MB | 3.55 MB | 3.49 MB | 910.45 kB | 915.50 kB | typescript.js +10.95 MB | 3.55 MB | 3.49 MB | 910.48 kB | 915.50 kB | typescript.js