Repository: sveltejs/svelte (JavaScript)
Date: 2022-02-07 19:24:55+00:00
Task type: Bug Fix

Issue:
`export enum` in Typescript ends up producing a Svelte compiler warning
### Describe the bug

I'm not at all confident that this issue belongs in this repo, since it could plausibly go into several different ones, but I thought I'd file it here to begin with since it's ultimately the Svelte compiler which is generating the warning and there is no way to ignore it (adding the ability to silence individual compiler warnings like this in JS, similar to the `<!-- svelte-ignore -->` template comment, would be one way of resolving this).

Anyway, the issue: Typescript code with `export enum` from inside `<script context="module" lang="ts">` block ends up generating an `unused-export-let` warning; see https://github.com/sveltejs/language-tools/issues/573 While the language-tools project added special handling to deal with this as an editor warning, it still generates warnings in other parts of the ecosystem. Users of my library may see this from vite-plugin-svelte, for example: https://github.com/rgossiaux/svelte-headlessui/issues/25

Besides the ability to silence the warning, alternate resolutions could be:
* Modifying preprocessing so that the generated code is different and does not produce this warning;
* Modifying the check for this warning (if it's possible to do this safely)

### Reproduction

It's not a minimal repo, but you can repo this by installing https://github.com/rgossiaux/svelte-headlessui and running `npm run dev`. But I think something very basic should work too, along the lines of

```
<script lang="ts" context="module">
  export enum MyEnum {
    One,
    Two,
  }
</script>
```

### Logs

_No response_

### System Info

```shell
System:
    OS: macOS 12.1
    CPU: (10) arm64 Apple M1 Max
    Memory: 26.12 GB / 64.00 GB
    Shell: 3.3.1 - /opt/homebrew/bin/fish
  Binaries:
    Node: 16.8.0 - /var/folders/rn/2kdgb0jj25q2v656j3qc_ywr0000gn/T/fnm_multishells/23662_1640400023652/bin/node
    Yarn: 1.22.17 - /opt/homebrew/bin/yarn
    npm: 7.21.0 - /var/folders/rn/2kdgb0jj25q2v656j3qc_ywr0000gn/T/fnm_multishells/23662_1640400023652/bin/npm
  Browsers:
    Chrome: 96.0.4664.110
    Safari: 15.2
  npmPackages:
    svelte: ^3.44.0 => 3.44.3
```


### Severity

annoyance


Issue discussion:
Here's how I'm dealing with this in `rollup.config.js`. Unfortunately, having to list the individual enums makes this an undesirable hack. I didn't see a way to reliably detect the presence of an enum in the frame.

```js
  plugins: [
    svelte({
      /* ... */
      onwarn: (warning, handler) => {
        const exportedEnums = ['InteractiveTreeFlags'];
        const { code, frame } = warning;
        const mentionsEnum = () =>
            exportedEnums.reduce((found, name) => (found || frame.includes(name)), false);
        if (code == "unused-export-let" && mentionsEnum()) {
          return;
        }
        handler(warning);
      }
    }),
```

Solution patch:
diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -519,7 +519,7 @@ export default class Component {
 		return result;
 	}
 
-	private _extract_exports(node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, module_script) {
+	private _extract_exports(node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, module_script: boolean) {
 		if (node.type === 'ExportDefaultDeclaration') {
 			return this.error(node as any, compiler_errors.default_export);
 		}
@@ -539,7 +539,7 @@ export default class Component {
 						extract_names(declarator.id).forEach(name => {
 							const variable = this.var_lookup.get(name);
 							variable.export_name = name;
-							if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
+							if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
 								this.warn(declarator as any, compiler_warnings.unused_export_let(this.name.name, name));
 							}
 						});
@@ -559,7 +559,7 @@ export default class Component {
 					if (variable) {
 						variable.export_name = specifier.exported.name;
 
-						if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
+						if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
 							this.warn(specifier as any, compiler_warnings.unused_export_let(this.name.name, specifier.exported.name));
 						}
 					}


Test patch:
diff --git a/test/validator/samples/unreferenced-variables/input.svelte b/test/validator/samples/unreferenced-variables/input.svelte
--- a/test/validator/samples/unreferenced-variables/input.svelte
+++ b/test/validator/samples/unreferenced-variables/input.svelte
@@ -1,25 +1,48 @@
+<script context="module">
+  var a1 = 1;
+  let b1 = 1;
+  const c1 = 1;
+  var d1 = 1;
+  let e1 = 1;
+  const f1 = 1;
+  export { d1, e1, f1};
+  export var g1 = 1;
+  export let h1 = 1;
+  export const i1 = 1;
+  export let j1 = () => {};
+  export const k1 = () => {};
+  export function l1() {};
+  var m1 = 1;
+  let n1 = 1;
+  const o1 = 1;
+  function foo1() {
+    return m1 + n1 + o1;
+  }
+  export let p1;
+</script>
+
 <script>
-  var a = 1;
-  let b = 1;
-  const c = 1;
-  var d = 1;
-  let e = 1;
-  const f = 1;
-  export { d, e, f};
-  export var g = 1;
-  export let h = 1;
-  export const i = 1;
-  export let j = () => {};
-  export const k = () => {};
-  export function l() {};
-  var m = 1;
-  let n = 1;
-  const o = 1;
-  function foo() {
-    return m + n + o;
+  var a2 = 1;
+  let b2 = 1;
+  const c2 = 1;
+  var d2 = 1;
+  let e2 = 1;
+  const f2 = 1;
+  export { d2, e2, f2};
+  export var g2 = 1;
+  export let h2 = 1;
+  export const i2 = 1;
+  export let j2 = () => {};
+  export const k2 = () => {};
+  export function l2() {};
+  var m2 = 1;
+  let n2 = 1;
+  const o2 = 1;
+  function foo2() {
+    return m2 + n2 + o2;
   }
-  export let p;
-  export let q;
-  $p;
+  export let p2;
+  export let q2;
+  $p2;
 </script>
-{$q}
+{$q2}
diff --git a/test/validator/samples/unreferenced-variables/warnings.json b/test/validator/samples/unreferenced-variables/warnings.json
--- a/test/validator/samples/unreferenced-variables/warnings.json
+++ b/test/validator/samples/unreferenced-variables/warnings.json
@@ -2,76 +2,76 @@
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 103,
-			"column": 12,
-			"line": 8
+			"character": 519,
+			"column": 13,
+			"line": 31
 		},
-		"message": "Component has unused export property 'd'. If it is for external reference only, please consider using `export const d`",
-		"pos": 102,
+		"message": "Component has unused export property 'd2'. If it is for external reference only, please consider using `export const d2`",
+		"pos": 517,
 		"start": {
-			"character": 102,
+			"character": 517,
 			"column": 11,
-			"line": 8
+			"line": 31
 		}
 	},
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 106,
-			"column": 15,
-			"line": 8
+			"character": 523,
+			"column": 17,
+			"line": 31
 		},
-		"message": "Component has unused export property 'e'. If it is for external reference only, please consider using `export const e`",
-		"pos": 105,
+		"message": "Component has unused export property 'e2'. If it is for external reference only, please consider using `export const e2`",
+		"pos": 521,
 		"start": {
-			"character": 105,
-			"column": 14,
-			"line": 8
+			"character": 521,
+			"column": 15,
+			"line": 31
 		}
 	},
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 130,
-			"column": 18,
-			"line": 9
+			"character": 549,
+			"column": 19,
+			"line": 32
 		},
-		"message": "Component has unused export property 'g'. If it is for external reference only, please consider using `export const g`",
-		"pos": 125,
+		"message": "Component has unused export property 'g2'. If it is for external reference only, please consider using `export const g2`",
+		"pos": 543,
 		"start": {
-			"character": 125,
+			"character": 543,
 			"column": 13,
-			"line": 9
+			"line": 32
 		}
 	},
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 150,
-			"column": 18,
-			"line": 10
+			"character": 570,
+			"column": 19,
+			"line": 33
 		},
-		"message": "Component has unused export property 'h'. If it is for external reference only, please consider using `export const h`",
-		"pos": 145,
+		"message": "Component has unused export property 'h2'. If it is for external reference only, please consider using `export const h2`",
+		"pos": 564,
 		"start": {
-			"character": 145,
+			"character": 564,
 			"column": 13,
-			"line": 10
+			"line": 33
 		}
 	},
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 199,
-			"column": 25,
-			"line": 12
+			"character": 621,
+			"column": 26,
+			"line": 35
 		},
-		"message": "Component has unused export property 'j'. If it is for external reference only, please consider using `export const j`",
-		"pos": 187,
+		"message": "Component has unused export property 'j2'. If it is for external reference only, please consider using `export const j2`",
+		"pos": 608,
 		"start": {
-			"character": 187,
+			"character": 608,
 			"column": 13,
-			"line": 12
+			"line": 35
 		}
 	}
 ]