From 0eb32a67708e6854dde6ec014c0f0c1642b94c76 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:13:30 +0000 Subject: [PATCH] fix(traverse): invalid variable name generated by `generate_uid_based_on_node` (#5407) close: #5371 --- crates/oxc_traverse/src/context/identifier.rs | 58 +++++++++++++++++++ crates/oxc_traverse/src/context/mod.rs | 1 + crates/oxc_traverse/src/context/scoping.rs | 4 +- tasks/transform_conformance/oxc.snap.md | 10 +++- .../fixtures/invalid-variable-name/input.js | 1 + .../fixtures/invalid-variable-name/output.js | 2 + 6 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 crates/oxc_traverse/src/context/identifier.rs create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/invalid-variable-name/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/invalid-variable-name/output.js diff --git a/crates/oxc_traverse/src/context/identifier.rs b/crates/oxc_traverse/src/context/identifier.rs new file mode 100644 index 0000000000000..a325548b3120a --- /dev/null +++ b/crates/oxc_traverse/src/context/identifier.rs @@ -0,0 +1,58 @@ +use std::borrow::Cow; + +use oxc_syntax::identifier::{is_identifier_name, is_identifier_part, is_identifier_start}; + +/// Convert a str to a valid identifier name. +/// +/// Based on Babel's [`toIdentifier`](https://github.com/babel/babel/blob/3bcfee232506a4cebe410f02042fb0f0adeeb0b1/packages/babel-types/src/converters/toIdentifier.ts#L4-L26) function. +pub fn to_identifier(input: &str) -> Cow { + if is_identifier_name(input) { + return Cow::Borrowed(input); + } + + let mut name = String::with_capacity(input.len()); + + let mut capitalize_next = false; + + let mut chars = input.chars(); + if let Some(first) = chars.next() { + if is_identifier_start(first) { + name.push(first); + } else { + capitalize_next = true; + } + } + + for c in chars { + if !is_identifier_part(c) { + capitalize_next = true; + } else if capitalize_next { + name.push(c.to_ascii_uppercase()); + capitalize_next = false; + } else { + name.push(c); + } + } + + if name.is_empty() { + return Cow::Borrowed("_"); + } + + Cow::Owned(name) +} + +#[test] +fn test() { + assert_eq!(to_identifier("foo"), "foo"); + assert_eq!(to_identifier("fooBar"), "fooBar"); + assert_eq!(to_identifier("fooBar1"), "fooBar1"); + + assert_eq!(to_identifier("foo-bar"), "fooBar"); + assert_eq!(to_identifier("foo bar"), "fooBar"); + assert_eq!(to_identifier("foo-bar-1"), "fooBar1"); + assert_eq!(to_identifier("1foo-bar"), "FooBar"); + assert_eq!(to_identifier("1-foo-bar"), "FooBar"); + assert_eq!(to_identifier("-- --"), "_"); + + assert_eq!(to_identifier("_output$headers$x-amzn-requestid"), "_output$headers$xAmznRequestid"); +} diff --git a/crates/oxc_traverse/src/context/mod.rs b/crates/oxc_traverse/src/context/mod.rs index 5714a0abe00f3..9ad60713c482b 100644 --- a/crates/oxc_traverse/src/context/mod.rs +++ b/crates/oxc_traverse/src/context/mod.rs @@ -16,6 +16,7 @@ mod ancestry; mod ast_operations; use ancestry::PopToken; pub use ancestry::TraverseAncestry; +mod identifier; mod scoping; pub use scoping::TraverseScoping; diff --git a/crates/oxc_traverse/src/context/scoping.rs b/crates/oxc_traverse/src/context/scoping.rs index 2ad9c60b26800..eb1c4b94483da 100644 --- a/crates/oxc_traverse/src/context/scoping.rs +++ b/crates/oxc_traverse/src/context/scoping.rs @@ -11,7 +11,7 @@ use oxc_syntax::{ symbol::{SymbolFlags, SymbolId}, }; -use super::ast_operations::GatherNodeParts; +use super::{ast_operations::GatherNodeParts, identifier::to_identifier}; use crate::scopes_collector::ChildScopeCollector; /// Traverse scope context. @@ -248,7 +248,7 @@ impl TraverseScoping { parts.push_str(part); }); let name = if parts.is_empty() { "ref" } else { parts.trim_start_matches('_') }; - self.generate_uid(name, scope_id, flags) + self.generate_uid(&to_identifier(name.get(..20).unwrap_or(name)), scope_id, flags) } /// Generate UID in current scope based on node. diff --git a/tasks/transform_conformance/oxc.snap.md b/tasks/transform_conformance/oxc.snap.md index b2f771354835c..64901ac666397 100644 --- a/tasks/transform_conformance/oxc.snap.md +++ b/tasks/transform_conformance/oxc.snap.md @@ -1,13 +1,19 @@ commit: 3bcfee23 -Passed: 11/40 +Passed: 11/41 # All Passed: * babel-plugin-transform-optional-catch-binding * babel-preset-typescript -# babel-plugin-transform-nullish-coalescing-operator (0/1) +# babel-plugin-transform-nullish-coalescing-operator (0/2) +* invalid-variable-name/input.js + x Reference flags mismatch: + | after transform: ReferenceId(3): ReferenceFlags(Write) + | rebuilt : ReferenceId(0): ReferenceFlags(Read | Write) + + * transform-in-arrow-function-expression/input.js x Reference flags mismatch: | after transform: ReferenceId(3): ReferenceFlags(Write) diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/invalid-variable-name/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/invalid-variable-name/input.js new file mode 100644 index 0000000000000..ee5c86e51b134 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/invalid-variable-name/input.js @@ -0,0 +1 @@ +out.head["foo-bar-qux"] ?? out.head["moon-unit"] diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/invalid-variable-name/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/invalid-variable-name/output.js new file mode 100644 index 0000000000000..541084042d189 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/invalid-variable-name/output.js @@ -0,0 +1,2 @@ +var _out$head$fooBarQux; +(_out$head$fooBarQux = out.head["foo-bar-qux"]) !== null && _out$head$fooBarQux !== void 0 ? _out$head$fooBarQux : out.head["moon-unit"];