Repository: vuejs/eslint-plugin-vue (JavaScript)

Issue:
 [no-export-in-script-setup] allow to export typescript interfaces/types
**What rule do you want to change?**

https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-export-in-script-setup.js
https://eslint.vuejs.org/rules/no-export-in-script-setup.html

**Does this change cause the rule to produce more or fewer warnings?**

no

**How will the change be implemented? (New option, new default behavior, etc.)?**
A new option should be enough, but I think it should be enabled by default

**Please provide some example code that this change will affect:**

<!-- Put your code examples here -->
```vue
<script setup lang="ts">
import { ref } from 'vue'

// this should be allowed
export type ComponentItem = {
  name: string,
}

// this should be allowed too
export interface ComponentProps {
  items: ComponentItem[]
}

const props = withDefaults(defineProps<ComponentProps>(), {
  items: () => [],
})
</script>
```

**What does the rule currently do for this code?**
The rules reject the export, regardless what is exported

**What will the rule do after it's changed?**
When `lang` of script is set to `ts`, allow exporting either `type` or `interface`





Discussion:

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 456376308..e695f422a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -25,7 +25,7 @@ jobs:
       #       - v2-npm-lock-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }}
       - run:
           name: Install dependencies
-          command: npm install
+          command: npm install --legacy-peer-deps
       - run:
           name: Test
           command: npm test
diff --git a/lib/rules/no-export-in-script-setup.js b/lib/rules/no-export-in-script-setup.js
index b39231dfe..99b6ba113 100644
--- a/lib/rules/no-export-in-script-setup.js
+++ b/lib/rules/no-export-in-script-setup.js
@@ -6,6 +6,12 @@
 
 const utils = require('../utils')
 
+/**
+ * @typedef {import('@typescript-eslint/types').TSESTree.ExportAllDeclaration} TSESTreeExportAllDeclaration
+ * @typedef {import('@typescript-eslint/types').TSESTree.ExportDefaultDeclaration} TSESTreeExportDefaultDeclaration
+ * @typedef {import('@typescript-eslint/types').TSESTree.ExportNamedDeclaration} TSESTreeExportNamedDeclaration
+ */
+
 module.exports = {
   meta: {
     type: 'problem',
@@ -23,7 +29,22 @@ module.exports = {
   /** @param {RuleContext} context */
   create(context) {
     /** @param {ExportAllDeclaration | ExportDefaultDeclaration | ExportNamedDeclaration} node */
-    function report(node) {
+    function verify(node) {
+      const tsNode =
+        /** @type {TSESTreeExportAllDeclaration | TSESTreeExportDefaultDeclaration | TSESTreeExportNamedDeclaration} */ (
+          node
+        )
+      if (tsNode.exportKind === 'type') {
+        return
+      }
+      if (tsNode.type === 'ExportNamedDeclaration') {
+        if (
+          tsNode.specifiers.length > 0 &&
+          tsNode.specifiers.every((spec) => spec.exportKind === 'type')
+        ) {
+          return
+        }
+      }
       context.report({
         node,
         messageId: 'forbidden'
@@ -31,9 +52,9 @@ module.exports = {
     }
 
     return utils.defineScriptSetupVisitor(context, {
-      ExportAllDeclaration: report,
-      ExportDefaultDeclaration: report,
-      ExportNamedDeclaration: report
+      ExportAllDeclaration: verify,
+      ExportDefaultDeclaration: verify,
+      ExportNamedDeclaration: verify
     })
   }
 }
diff --git a/package.json b/package.json
index c2eefdb34..f3abcbcdb 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
     "@types/natural-compare": "^1.4.0",
     "@types/node": "^13.13.5",
     "@types/semver": "^7.2.0",
-    "@typescript-eslint/parser": "^5.0.0-0",
+    "@typescript-eslint/parser": "^5.4.0",
     "@vuepress/plugin-pwa": "^1.4.1",
     "acorn": "^8.5.0",
     "env-cmd": "^10.1.0",
@@ -82,7 +82,7 @@
     "mocha": "^7.1.2",
     "nyc": "^15.1.0",
     "prettier": "^2.4.1",
-    "typescript": "^4.4.3",
+    "typescript": "^4.5.0-0",
     "vue-eslint-editor": "^1.1.0",
     "vuepress": "^1.8.2"
   }


Test patch:
diff --git a/tests/lib/rules/no-export-in-script-setup.js b/tests/lib/rules/no-export-in-script-setup.js
index 5bc759f4c..3ea915401 100644
--- a/tests/lib/rules/no-export-in-script-setup.js
+++ b/tests/lib/rules/no-export-in-script-setup.js
@@ -8,6 +8,7 @@
 // Requirements
 // ------------------------------------------------------------------------------
 
+const semver = require('semver')
 const eslint = require('eslint')
 const rule = require('../../../lib/rules/no-export-in-script-setup')
 
@@ -48,7 +49,43 @@ ruleTester.run('no-export-in-script-setup', rule, {
       let foo;
       </script>
       `
-    }
+    },
+    ...(semver.gte(
+      require('@typescript-eslint/parser/package.json').version,
+      '5.4.0'
+    ) &&
+    semver.satisfies(require('typescript/package.json').version, '>=4.5.0-0')
+      ? [
+          {
+            filename: 'test.vue',
+            code: `
+            <script setup lang="ts">
+            export { type Foo } from "foo"
+            export type Bar = {}
+            export interface Bar {}
+            </script>
+            `,
+            parser: require.resolve('vue-eslint-parser'),
+            parserOptions: {
+              parser: require.resolve('@typescript-eslint/parser')
+            }
+          }
+        ]
+      : [
+          {
+            filename: 'test.vue',
+            code: `
+            <script setup lang="ts">
+            export type Bar = {}
+            export interface Bar {}
+            </script>
+            `,
+            parser: require.resolve('vue-eslint-parser'),
+            parserOptions: {
+              parser: require.resolve('@typescript-eslint/parser')
+            }
+          }
+        ])
   ],
 
   invalid: [
@@ -102,6 +139,34 @@ ruleTester.run('no-export-in-script-setup', rule, {
           line: 8
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      export const Foo = {}
+      export enum Bar {}
+      export {}
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions: {
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      errors: [
+        {
+          message: '`<script setup>` cannot contain ES module exports.',
+          line: 3
+        },
+        {
+          message: '`<script setup>` cannot contain ES module exports.',
+          line: 4
+        },
+        {
+          message: '`<script setup>` cannot contain ES module exports.',
+          line: 5
+        }
+      ]
     }
   ]
 })