Skip to content

Commit

Permalink
Merge branch 'main' into no-confusing-no-confusing-non-null-assertion
Browse files Browse the repository at this point in the history
  • Loading branch information
DonIsaac committed Aug 5, 2024
2 parents 361ce0f + cbf08d2 commit 853d76e
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 26 deletions.
12 changes: 11 additions & 1 deletion crates/oxc_linter/src/rules/eslint/no_multi_str.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::AstNodeId;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, AstNode};
Expand Down Expand Up @@ -38,7 +39,7 @@ impl Rule for NoMultiStr {
// https://github.com/eslint/eslint/blob/9e6d6405c3ee774c2e716a3453ede9696ced1be7/lib/shared/ast-utils.js#L12
let position =
source.find(|ch| matches!(ch, '\r' | '\n' | '\u{2028}' | '\u{2029}')).unwrap_or(0);
if position != 0 {
if position != 0 && !is_within_jsx_attribute_item(node.id(), ctx) {
// We found the "newline" character but want to highlight the '\', so go back one
// character.
let multi_span_start =
Expand All @@ -52,6 +53,13 @@ impl Rule for NoMultiStr {
}
}

fn is_within_jsx_attribute_item(id: AstNodeId, ctx: &LintContext) -> bool {
if matches!(ctx.nodes().parent_kind(id), Some(AstKind::JSXAttributeItem(_))) {
return true;
}
false
}

#[test]
fn test() {
use crate::tester::Tester;
Expand All @@ -61,6 +69,8 @@ fn test() {
"var a = <div>
<h1>Wat</h1>
</div>;", // { "ecmaVersion": 6, "parserOptions": { "ecmaFeatures": { "jsx": true } } }
r#"<div class="line1
line2"></div>"#, // jsx
];

let fail = vec![
Expand Down
93 changes: 83 additions & 10 deletions crates/oxc_minifier/src/ast_passes/replace_global_defines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ pub struct ReplaceGlobalDefinesConfig(Arc<ReplaceGlobalDefinesConfigImpl>);
#[derive(Debug)]
struct ReplaceGlobalDefinesConfigImpl {
identifier_defines: Vec<(/* key */ String, /* value */ String)>,
// TODO: dot defines
dot_defines: Vec<(/* member expression parts */ Vec<String>, /* value */ String)>,
}

enum IdentifierType {
Identifier,
DotDefines(Vec<String>),
}

impl ReplaceGlobalDefinesConfig {
Expand All @@ -30,21 +35,45 @@ impl ReplaceGlobalDefinesConfig {
pub fn new<S: AsRef<str>>(defines: &[(S, S)]) -> Result<Self, Vec<OxcDiagnostic>> {
let allocator = Allocator::default();
let mut identifier_defines = vec![];
let mut dot_defines = vec![];
for (key, value) in defines {
let key = key.as_ref();

let value = value.as_ref();
Self::check_key(key)?;
Self::check_value(&allocator, value)?;
identifier_defines.push((key.to_string(), value.to_string()));

match Self::check_key(key)? {
IdentifierType::Identifier => {
identifier_defines.push((key.to_string(), value.to_string()));
}
IdentifierType::DotDefines(parts) => {
dot_defines.push((parts, value.to_string()));
}
}
}
Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl { identifier_defines })))

Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl { identifier_defines, dot_defines })))
}

fn check_key(key: &str) -> Result<(), Vec<OxcDiagnostic>> {
if !is_identifier_name(key) {
return Err(vec![OxcDiagnostic::error(format!("`{key}` is not an identifier."))]);
fn check_key(key: &str) -> Result<IdentifierType, Vec<OxcDiagnostic>> {
let parts: Vec<&str> = key.split('.').collect();

assert!(!parts.is_empty());

if parts.len() == 1 {
if !is_identifier_name(parts[0]) {
return Err(vec![OxcDiagnostic::error(format!("`{key}` is not an identifier."))]);
}
return Ok(IdentifierType::Identifier);
}
Ok(())

for part in &parts {
if !is_identifier_name(part) {
return Err(vec![OxcDiagnostic::error(format!("`{key}` is not an identifier."))]);
}
}

Ok(IdentifierType::DotDefines(parts.iter().map(ToString::to_string).collect()))
}

fn check_value(allocator: &Allocator, source_text: &str) -> Result<(), Vec<OxcDiagnostic>> {
Expand All @@ -59,6 +88,7 @@ impl ReplaceGlobalDefinesConfig {
///
/// * <https://esbuild.github.io/api/#define>
/// * <https://github.com/terser/terser?tab=readme-ov-file#conditional-compilation>
/// * <https://github.com/evanw/esbuild/blob/9c13ae1f06dfa909eb4a53882e3b7e4216a503fe/internal/config/globals.go#L852-L1014>
pub struct ReplaceGlobalDefines<'a> {
ast: AstBuilder<'a>,
config: ReplaceGlobalDefinesConfig,
Expand All @@ -84,8 +114,8 @@ impl<'a> ReplaceGlobalDefines<'a> {
}

fn replace_identifier_defines(&self, expr: &mut Expression<'a>) {
for (key, value) in &self.config.0.identifier_defines {
if let Expression::Identifier(ident) = expr {
if let Expression::Identifier(ident) = expr {
for (key, value) in &self.config.0.identifier_defines {
if ident.name.as_str() == key {
let value = self.parse_value(value);
*expr = value;
Expand All @@ -94,11 +124,54 @@ impl<'a> ReplaceGlobalDefines<'a> {
}
}
}

fn replace_dot_defines(&self, expr: &mut Expression<'a>) {
if let Expression::StaticMemberExpression(member) = expr {
'outer: for (parts, value) in &self.config.0.dot_defines {
assert!(parts.len() > 1);

let mut current_part_member_expression = Some(&*member);
let mut cur_part_name = &member.property.name;

for (i, part) in parts.iter().enumerate().rev() {
if cur_part_name.as_str() != part {
continue 'outer;
}

if i == 0 {
break;
}

current_part_member_expression =
if let Some(member) = current_part_member_expression {
match &member.object.without_parenthesized() {
Expression::StaticMemberExpression(member) => {
cur_part_name = &member.property.name;
Some(member)
}
Expression::Identifier(ident) => {
cur_part_name = &ident.name;
None
}
_ => None,
}
} else {
continue 'outer;
};
}

let value = self.parse_value(value);
*expr = value;
break;
}
}
}
}

impl<'a> VisitMut<'a> for ReplaceGlobalDefines<'a> {
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
self.replace_identifier_defines(expr);
self.replace_dot_defines(expr);
walk_mut::walk_expression(self, expr);
}
}
6 changes: 5 additions & 1 deletion crates/oxc_minifier/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ pub(crate) fn test_with_options(source_text: &str, expected: &str, options: Comp
);
}

fn run(source_text: &str, source_type: SourceType, options: Option<CompressOptions>) -> String {
pub(crate) fn run(
source_text: &str,
source_type: SourceType,
options: Option<CompressOptions>,
) -> String {
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let program = allocator.alloc(ret.program);
Expand Down
46 changes: 32 additions & 14 deletions crates/oxc_minifier/tests/oxc/replace_global_defines.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
use oxc_allocator::Allocator;
use oxc_codegen::{CodegenOptions, WhitespaceRemover};
use oxc_codegen::{CodeGenerator, CodegenOptions};
use oxc_minifier::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig};
use oxc_parser::Parser;
use oxc_span::SourceType;

use crate::run;

pub(crate) fn test(source_text: &str, expected: &str, config: ReplaceGlobalDefinesConfig) {
let minified = {
let source_type = SourceType::default();
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let program = allocator.alloc(ret.program);
ReplaceGlobalDefines::new(&allocator, config).build(program);
WhitespaceRemover::new()
.with_options(CodegenOptions { single_quote: true })
.build(program)
.source_text
};
assert_eq!(minified, expected, "for source {source_text}");
let source_type = SourceType::default();
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let program = allocator.alloc(ret.program);
ReplaceGlobalDefines::new(&allocator, config).build(program);
let result = CodeGenerator::new()
.with_options(CodegenOptions { single_quote: true })
.build(program)
.source_text;
let expected = run(expected, source_type, None);
assert_eq!(result, expected, "for source {source_text}");
}

#[test]
fn replace_global_definitions() {
let config = ReplaceGlobalDefinesConfig::new(&[("id", "text"), ("str", "'text'")]).unwrap();
test("id, str", "text,'text';", config);
test("id, str", "text, 'text'", config);
}

#[test]
fn replace_global_definitions_dot() {
{
let config =
ReplaceGlobalDefinesConfig::new(&[("process.env.NODE_ENV", "production")]).unwrap();
test("process.env.NODE_ENV", "production", config.clone());
test("process.env", "process.env", config.clone());
test("process.env.foo.bar", "process.env.foo.bar", config.clone());
test("process", "process", config);
}

{
let config = ReplaceGlobalDefinesConfig::new(&[("process", "production")]).unwrap();
test("foo.process.NODE_ENV", "foo.process.NODE_ENV", config);
}
}

0 comments on commit 853d76e

Please sign in to comment.