vuejs/vue-eslint-parser

`<script setup>` imports are not hoisted in the presence of a `<script>`

Opened this issue · 0 comments

Before You File a Bug Report Please Confirm You Have Done The Following...

  • I'm using eslint-plugin-vue.
  • I'm sure the problem is a parser problem. (If you are not sure, search for the issue in eslint-plugin-vue repo and open the issue in eslint-plugin-vue repo if there is no solution.
  • I have tried restarting my IDE and the issue persists.
  • I have updated to the latest version of the packages.

What version of ESLint are you using?

8.57

What version of eslint-plugin-vue and vue-eslint-parser are you using?

  • vue-eslint-parser@10.1.1
  • eslint-plugin-vue@10.0.0

What did you do?

Configuration
import pluginVue from 'eslint-plugin-vue';
import globals from 'globals';
import eslintPluginImportX from 'eslint-plugin-import-x';

export default [
    ...pluginVue.configs['flat/recommended'],
    {
        rules: {
            'vue/multi-word-component-names': 'off',
            'import-x/first': 'error',
        },
        languageOptions: {
            sourceType: 'module',
            globals: {
                ...globals.browser
            }
        }
    },
    eslintPluginImportX.flatConfigs.recommended
];
<script>
import { normalize } from 'node:path';
console.log('Hello, ')
</script>

<script setup>
import { readFileSync } from 'node:fs';
console.log('World!')
</script>

What did you expect to happen?

The parser should generate an AST similarly to how Vue itself transpiles the code, with the import statements of <script setup> hoisted up to the top of the file with the <script> imports:

  • ImportDeclaration
  • ImportDeclaration
  • ExpressionStatement
  • ExpressionStatement

What actually happened?

The AST given to rule providers instead has all of the <script> above the <script setup> imports:

  • ImportDeclaration
  • ExpressionStatement
  • ImportDeclaration
  • ExpressionStatement

This conflicts with some rules, like eslint-plugin-import-x's defined import-x/first, which believes the <script setup> has a misplaced import.

Link to Minimal Reproducible Example

https://github.com/Kenneth-Sills/vue-eslint-parser-script-import-reproduction

Additional comments

Taking a look at the source, I see that getScriptSetupCodeBlocks does correctly hoist the import statements to the top of it's returned CodeBlocks, but in the presence of a scriptElement in getScriptSetupModuleCodeBlocks it's just appended to the CodeBlocks generated from the <script>.

Unfortunately, the fix will be a little more complicated than simply relocating the ImportDeclarations because there's a fair bit of remapping code built into these functions and the callers thereof that assumes the <script setup> block is one contiguous segment inside the generated combined CodeBlocks.