Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changepacks/changepack_log_next_webpack_js_jsx_maps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch",
"packages/webpack-plugin/package.json": "Patch",
"packages/next-plugin/package.json": "Patch"
},
"note": "Accept JSX in .js, .mjs and .cjs throughout extraction without changing TypeScript dialects or plain JavaScript output, and pass parsed source-map objects to webpack callbacks for Next SWC compatibility. Malformed relevant source now fails with located parser diagnostics instead of unlocated parser-panic messages or silently recovered output.",
"date": "2026-10-15T00:00:00.000Z"
}
4 changes: 2 additions & 2 deletions libs/extractor/src/build_time_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use oxc_ast::ast::{
use oxc_ast_visit::{Visit, walk};
use oxc_parser::Parser;
use oxc_semantic::{Scoping, SemanticBuilder};
use oxc_span::{GetSpan, SourceType, Span};
use oxc_span::{GetSpan, Span};
use oxc_syntax::operator::LogicalOperator;
use oxc_syntax::reference::ReferenceId;
use oxc_syntax::symbol::SymbolId;
Expand Down Expand Up @@ -131,7 +131,7 @@ const UNCERTAIN_MEMBERS: [&str; 9] = [
];

fn parse<'a>(allocator: &'a Allocator, filename: &str, code: &'a str) -> Option<Program<'a>> {
let source_type = SourceType::from_path(filename).ok()?;
let source_type = crate::parsing::source_type(filename).ok()?;
let parsed = Parser::new(allocator, code, source_type).parse();
(!parsed.fatal_error).then_some(parsed.program)
}
Expand Down
4 changes: 2 additions & 2 deletions libs/extractor/src/emotion_namespace/driver.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
Allocator, BTreeMap, Binding, Cow, Errors, GetSpan, HashMap, HashSet, ImportAlias, Normalized,
Normalizer, Parser, Root, SemanticBuilder, SourceType, Span, identifier_names, macro_name,
Normalizer, Parser, Root, SemanticBuilder, Span, identifier_names, macro_name,
};
use oxc_ast::ast::{
Declaration, ImportDeclaration, ImportDeclarationSpecifier, Statement,
Expand Down Expand Up @@ -117,7 +117,7 @@ pub(crate) fn normalize<'a>(
let parsed = Parser::new(
&allocator,
code,
SourceType::from_path(filename).unwrap_or_default(),
crate::parsing::source_type(filename).unwrap_or_default(),
)
.parse();
let semantic = SemanticBuilder::new()
Expand Down
4 changes: 2 additions & 2 deletions libs/extractor/src/emotion_namespace/output.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{Allocator, Edit, Parser, SourceType};
use super::{Allocator, Edit, Parser};
use crate::{ExtractOutput, source_map};
use oxc_codegen::{Codegen, CodegenOptions};
use rustc_hash::FxHashSet;
Expand All @@ -21,7 +21,7 @@ impl Rewritten<'_> {
let program = Parser::new(
&allocator,
self.code,
SourceType::from_path(self.filename).unwrap_or_default(),
crate::parsing::source_type(self.filename).unwrap_or_default(),
)
.parse()
.program;
Expand Down
23 changes: 18 additions & 5 deletions libs/extractor/src/emotion_namespace_tests/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,30 @@ use rstest::rstest;

#[test]
#[serial]
fn emotion_restoration_reports_malformed_source_when_normalization_rejects_a_read() {
// Given: normalization can reject a namespace read before the main parser runs.
fn syntax_errors_are_reported_before_namespace_normalization() {
// Given: malformed source also contains an unsupported namespace read.
let source = "import * as E from '@emotion/css';\nE.__emotion_user(); const broken;";

// When
let error = compile(source).unwrap_err();

// Then
assert!(error.starts_with("namespace.tsx:2:1:"), "{error}");
assert!(error.contains("E.__emotion_user"), "{error}");
assert!(error.contains("Emotion diagnostic restoration failed:"), "{error}");
assert_eq!(error, "namespace.tsx:2:27: Missing initializer in const declaration");
}

#[test]
fn restoration_preserves_the_diagnostic_when_its_source_is_malformed() {
// Given: an internal generated-name diagnostic and source the parser rejects.
let source = "import * as E from '@emotion/css';\nE.__emotion_user(); const broken;";
let message = utils::build_time_error("css", "__emotion_css_0()", "pass a static style object");

// When: diagnostic restoration cannot safely inspect the original AST.
let error = emotion_namespace::original_error_code(source, source.find("E.__emotion_user").unwrap(), message.clone(), SourceType::tsx());

// Then: the original diagnostic survives together with the parser failure.
assert_eq!(error.lines().next(), Some(message.as_str()));
assert!(error.contains("Emotion diagnostic restoration failed:"));
assert!(error.contains("Missing initializer in const declaration"));
}

#[test]
Expand Down
9 changes: 7 additions & 2 deletions libs/extractor/src/extractor/styled_jsx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use oxc_ast::{
builder::AstBuilder,
};
use oxc_ast_visit::Visit;
use oxc_span::SPAN;
use oxc_span::{FileExtension, SPAN};
use rustc_hash::FxHashSet;

#[cfg(test)]
Expand Down Expand Up @@ -38,7 +38,12 @@ impl<'a> Visit<'a> for Names<'a> {
impl StyledJsx {
/// Reserve a name across all scopes before extraction removes or rewrites nodes.
pub(crate) fn reserve(program: &Program<'_>) -> Option<Self> {
if program.source_type.is_jsx() {
if program.source_type.is_jsx()
&& !matches!(
program.source_type.extension(),
Some(FileExtension::Js | FileExtension::Mjs | FileExtension::Cjs)
)
{
return None;
}
let mut names = Names::default();
Expand Down
10 changes: 6 additions & 4 deletions libs/extractor/src/import_alias_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ use oxc_ast_visit::{
walk::{walk_call_expression, walk_jsx_opening_element},
};
use oxc_parser::Parser;
use oxc_span::{GetSpan, SourceType};
use oxc_span::GetSpan;
#[cfg(test)]
use oxc_span::SourceType;
use std::borrow::Cow;
use std::collections::HashMap;

Expand Down Expand Up @@ -252,7 +254,7 @@ pub fn companion_errors(code: &str, filename: &str) -> Vec<(u32, String)> {
return Vec::new();
}
let allocator = Allocator::default();
let source_type = SourceType::from_path(filename).unwrap_or_default();
let source_type = crate::parsing::source_type(filename).unwrap_or_default();
let program = Parser::new(&allocator, code, source_type).parse().program;
program
.body
Expand Down Expand Up @@ -302,7 +304,7 @@ pub fn transform_import_aliases_with_edits<'a>(
}

let allocator = Allocator::default();
let source_type = SourceType::from_path(filename).unwrap_or_default();
let source_type = crate::parsing::source_type(filename).unwrap_or_default();

// Parse the code
let parser_ret = Parser::new(&allocator, code, source_type).parse();
Expand Down Expand Up @@ -401,7 +403,7 @@ pub(super) fn pixelify_emotion_values<'a>(
original: &str,
) -> (Cow<'a, str>, Vec<Edit>) {
let allocator = Allocator::default();
let source_type = SourceType::from_path(filename).unwrap_or_default();
let source_type = crate::parsing::source_type(filename).unwrap_or_default();
let original_program = Parser::new(&allocator, original, source_type)
.parse()
.program;
Expand Down
2 changes: 1 addition & 1 deletion libs/extractor/src/imported_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ impl Modules<'_> {

fn read(&mut self, path: &str, code: &str) -> FxHashMap<String, Constant> {
let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap_or_else(|_| SourceType::ts());
let source_type = crate::parsing::source_type(path).unwrap_or_else(|_| SourceType::ts());
let program = Parser::new(&allocator, code, source_type).parse().program;
let mut scope = ModuleScope::new(path, &program, Some(code));
let mut exports = FxHashMap::default();
Expand Down
42 changes: 23 additions & 19 deletions libs/extractor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod import_alias_visit;
mod imported_constants;
mod module_loader;
mod mutations;
mod parsing;
mod prop_modify_utils;
mod source_map;
mod style_values;
Expand All @@ -37,6 +38,7 @@ use oxc_ast::ast::Expression;
use oxc_ast_visit::VisitMut;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::{Parser, ParserReturn};
#[cfg(test)]
use oxc_span::SourceType;
use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;
Expand Down Expand Up @@ -299,6 +301,16 @@ fn extract_with_source_map(
source_map: bool,
resolver: Option<&ModuleResolver>,
) -> Result<ExtractOutput, Box<dyn Error>> {
if code.contains(&option.package)
|| code.contains(STYLEX_PACKAGE)
|| option
.import_aliases
.keys()
.any(|alias| code.contains(alias))
{
let allocator = Allocator::default();
parsing::parse(&allocator, filename, code)?;
}
extract_source(filename, code, None, option, source_map, resolver)
}

Expand Down Expand Up @@ -440,7 +452,6 @@ fn extract_source(

let code_to_parse = processed_code.as_deref().unwrap_or(&transformed_code);

let source_type = SourceType::from_path(filename)?;
let (bucket, global, css_file) = resolve_css_target(filename, &option);
let import_main = option.import_main_css && !global;
// Presize to the exact final length (1 target + optional main-css entry) and
Expand All @@ -453,14 +464,7 @@ fn extract_source(
css_files.push(css_file.clone());
let allocator = Allocator::default();

let ParserReturn {
mut program, // AST
fatal_error, // Parser encountered an error it couldn't recover from
..
} = Parser::new(&allocator, code_to_parse, source_type).parse();
if fatal_error {
return Err("Parser panicked".into());
}
let mut program = parsing::parse(&allocator, filename, code_to_parse)?;
let inlined = if processed_code.is_none() {
imported_constants::inline_constants(
&oxc_ast::builder::AstBuilder::new(&allocator),
Expand Down Expand Up @@ -626,7 +630,7 @@ fn located_errors(
source,
offset,
message,
SourceType::from_path(filename).unwrap_or_default(),
parsing::source_type(filename).unwrap_or_default(),
);
format!("{}: {message}", locate(filename, source, offset))
})
Expand Down Expand Up @@ -709,7 +713,7 @@ fn extract_class_map_from_code(
option: &ExtractOption,
style_names: &FxHashSet<String>,
) -> Result<FxHashMap<String, String>, Box<dyn Error>> {
let source_type = SourceType::from_path(filename)?;
let source_type = parsing::source_type(filename)?;
let (bucket, global, css_file) = resolve_css_target(filename, option);
let css_files = vec![css_file];
let allocator = Allocator::default();
Expand Down Expand Up @@ -775,7 +779,7 @@ pub fn has_devup_ui(filename: &str, code: &str, package: &str) -> bool {
return false;
}

let source_type = match SourceType::from_path(filename) {
let source_type = match parsing::source_type(filename) {
Ok(st) => st,
Err(_) => return false,
};
Expand Down Expand Up @@ -1498,7 +1502,7 @@ mod tests {
extract(
"test.tsx",
r"import {Box} from '@devup-ui/core'
<Box padding={1} margin={2} wrong={} wrong2=<></> />
<Box padding={1} margin={2} wrong={null} wrong2=<></> />
",
ExtractOption {
package: "@devup-ui/core".to_string(),
Expand Down Expand Up @@ -1747,7 +1751,7 @@ mod tests {
extract(
"test.tsx",
r"import {Box as C} from '@devup-ui/core'
<C padding={1} margin={2} className={} />
<C padding={1} margin={2} />
",
ExtractOption {
package: "@devup-ui/core".to_string(),
Expand Down Expand Up @@ -4869,7 +4873,7 @@ import clsx from 'clsx'
)
.unwrap_err()
.to_string(),
"Parser panicked"
"test.tsx:1:11: Expected `from` but found `string`"
);
}

Expand Down Expand Up @@ -7002,7 +7006,7 @@ export {
extract(
"test.jsx",
r#"import {Box, css} from '@devup-ui/core'
<Box className={css({color:"white"})} styleOrder={} />
<Box className={css({color:"white"})} styleOrder={null} />
"#,
ExtractOption {
package: "@devup-ui/core".to_string(),
Expand Down Expand Up @@ -7745,7 +7749,7 @@ export { c as Lib };"#,
extract(
"test.jsx",
r"import {Box} from '@devup-ui/core'
<Box styleVars={} />
<Box styleVars />
",
ExtractOption {
package: "@devup-ui/core".to_string(),
Expand Down Expand Up @@ -9505,7 +9509,7 @@ keyframes({
extract(
"test.tsx",
r"import {Box} from '@devup-ui/core'
<Box padding={1} margin={2} wrong={} wrong2=<></> />
<Box padding={1} margin={2} wrong={null} wrong2=<></> />
",
ExtractOption {
package: "@devup-ui/core".to_string(),
Expand Down Expand Up @@ -9538,7 +9542,7 @@ keyframes({
extract(
"test1.tsx",
r"import {Box} from '@devup-ui/core'
<Box padding={1} margin={2} wrong={} wrong2=<></> />
<Box padding={1} margin={2} wrong={null} wrong2=<></> />
",
ExtractOption {
package: "@devup-ui/core".to_string(),
Expand Down
Loading
Loading