Repository: swc-project/swc (rust)

Issue:
[css/modules] class is disappear when composes is chained
### Describe the bug
The css module specification allows composes to be chained.
https://github.com/css-modules/css-modules#:~:text=When%20a%20class%20name%20composes%20another%20class%20name%2C%20the%20CSS%20Module%20exports%20both%20class%20names%20for%20the%20local%20class.
So when compile the following css, `root-class` should have `child-class` and `child-class2`.
However, swc_css_modules does not.


### Input code

```css
.child-class2 {
  color: red;
}

.child-class {
  composes: child-class2;
}

.root-class {
  composes: child-class;
}
```


### Config

```json
none
```


### Playground link

_No response_

### Expected behavior

```diff
{
  "child-class": [
    {
      "type": "local",
      "name": "__local__child-class"
    },
    {
      "type": "local",
      "name": "__local__child-class2"
    }
  ],
  "child-class2": [
    {
      "type": "local",
      "name": "__local__child-class2"
    }
  ],
  "root-class": [
    {
      "type": "local",
      "name": "__local__root-class"
    },
    {
      "type": "local",
      "name": "__local__child-class"
    },
+   {
+     "type": "local",
+     "name": "__local__child-class2"
+   }
  ]
}
```

### Actual behavior
https://github.com/swc-project/swc/commit/d862121c997ae2eecf3ad84308072d06106c66c2#diff-68c61c9e7905dcfc96fa4311d833c7da36303e22077b15074018b0f9c132606c
```diff
{
  "child-class": [
    {
      "type": "local",
      "name": "__local__child-class"
    },
    {
      "type": "local",
      "name": "__local__child-class2"
    }
  ],
  "child-class2": [
    {
      "type": "local",
      "name": "__local__child-class2"
    }
  ],
  "root-class": [
    {
      "type": "local",
      "name": "__local__root-class"
    },
    {
      "type": "local",
      "name": "__local__child-class"
    }
  ]
}
```

### Version

1.3.73

### Additional context

Transferred from https://github.com/web-infra-dev/rspack/issues/3875

Pull request description:
fix(css/modules): aggregate class names when composes is chained.
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**
aggregate class names when composes is chained.
<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**
Closes #7737
https://github.com/web-infra-dev/rspack/issues/3875

Patch:
diff --git a/crates/swc_css_modules/src/lib.rs b/crates/swc_css_modules/src/lib.rs
index 81f4da7056..cf84afd841 100644
--- a/crates/swc_css_modules/src/lib.rs
+++ b/crates/swc_css_modules/src/lib.rs
@@ -218,7 +218,27 @@ where
                                     .cloned();
 
                                 if let Some(key) = key {
-                                    self.result.renamed.entry(key).or_default().extend(composes);
+                                    let mut renamed = self.result.renamed.clone();
+                                    let class_names = self.result.renamed.entry(key).or_default();
+
+                                    class_names.extend(composes.clone());
+
+                                    for composed_class_name in composes.iter() {
+                                        if let CssClassName::Local { name } = composed_class_name {
+                                            if let Some(original_class_name) =
+                                                self.data.renamed_to_orig.get(&name.value)
+                                            {
+                                                class_names.extend(
+                                                    renamed
+                                                        .entry(original_class_name.clone())
+                                                        .or_default()
+                                                        .split_at(1)
+                                                        .1
+                                                        .to_vec(),
+                                                );
+                                            }
+                                        }
+                                    }
                                 }
                             }
                         }


Test patch:
diff --git a/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.compiled.css b/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.compiled.css
new file mode 100644
index 0000000000..85753b7f42
--- /dev/null
+++ b/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.compiled.css
@@ -0,0 +1,3 @@
+.__local__chain2 {}
+.__local__chain1 {}
+.__local__root-class {}
diff --git a/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.css b/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.css
new file mode 100644
index 0000000000..dd86250999
--- /dev/null
+++ b/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.css
@@ -0,0 +1,16 @@
+.chain2 {
+    composes: e from global;
+    composes: f from "./f.css";
+}
+
+.chain1 {
+    composes: chain2;
+    composes: c from global;
+    composes: d from "./d.css";
+}
+
+.root-class {
+    composes: chain1;
+    composes: a from global;
+    composes: b from "./b.css";
+}
diff --git a/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.imports.json b/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.imports.json
new file mode 100644
index 0000000000..77a9c3b849
--- /dev/null
+++ b/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.imports.json
@@ -0,0 +1,5 @@
+[
+  "./b.css",
+  "./d.css",
+  "./f.css"
+]
diff --git a/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.transform.json b/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.transform.json
new file mode 100644
index 0000000000..eb2ecd0fc4
--- /dev/null
+++ b/crates/swc_css_modules/tests/fixture/modules/issue-7737/issue-7737.transform.json
@@ -0,0 +1,86 @@
+{
+  "chain2": [
+    {
+      "type": "local",
+      "name": "__local__chain2"
+    },
+    {
+      "type": "global",
+      "name": "e"
+    },
+    {
+      "type": "import",
+      "name": "f",
+      "from": "./f.css"
+    }
+  ],
+  "chain1": [
+    {
+      "type": "local",
+      "name": "__local__chain1"
+    },
+    {
+      "type": "local",
+      "name": "__local__chain2"
+    },
+    {
+      "type": "global",
+      "name": "c"
+    },
+    {
+      "type": "import",
+      "name": "d",
+      "from": "./d.css"
+    },
+    {
+      "type": "global",
+      "name": "e"
+    },
+    {
+      "type": "import",
+      "name": "f",
+      "from": "./f.css"
+    }
+  ],
+  "root-class": [
+    {
+      "type": "local",
+      "name": "__local__root-class"
+    },
+    {
+      "type": "local",
+      "name": "__local__chain1"
+    },
+    {
+      "type": "global",
+      "name": "a"
+    },
+    {
+      "type": "import",
+      "name": "b",
+      "from": "./b.css"
+    },
+    {
+      "type": "local",
+      "name": "__local__chain2"
+    },
+    {
+      "type": "global",
+      "name": "c"
+    },
+    {
+      "type": "import",
+      "name": "d",
+      "from": "./d.css"
+    },
+    {
+      "type": "global",
+      "name": "e"
+    },
+    {
+      "type": "import",
+      "name": "f",
+      "from": "./f.css"
+    }
+  ]
+}
diff --git a/crates/swc_css_modules/tests/fixture/modules/tests-cases/composes-1/source.transform.json b/crates/swc_css_modules/tests/fixture/modules/tests-cases/composes-1/source.transform.json
index 9d2c41fdcb..6aa2e89e42 100644
--- a/crates/swc_css_modules/tests/fixture/modules/tests-cases/composes-1/source.transform.json
+++ b/crates/swc_css_modules/tests/fixture/modules/tests-cases/composes-1/source.transform.json
@@ -18,6 +18,11 @@
     {
       "type": "local",
       "name": "__local__c1"
+    },
+    {
+      "type": "import",
+      "name": "c2",
+      "from": "./file.css"
     }
   ],
   "c5": [
diff --git a/crates/swc_css_modules/tests/fixture/modules/tests-cases/composes-2/source.transform.json b/crates/swc_css_modules/tests/fixture/modules/tests-cases/composes-2/source.transform.json
index 74d54730c7..c7fcc9f0b7 100644
--- a/crates/swc_css_modules/tests/fixture/modules/tests-cases/composes-2/source.transform.json
+++ b/crates/swc_css_modules/tests/fixture/modules/tests-cases/composes-2/source.transform.json
@@ -18,6 +18,11 @@
     {
       "type": "local",
       "name": "__local__c1"
+    },
+    {
+      "type": "import",
+      "name": "c-2",
+      "from": "./file.css"
     }
   ],
   "c5": [