Skip to content

eslint-plugin-svelte crashes when used with [email protected] #1436

@MaddyGuthridge

Description

@MaddyGuthridge

In particular, this issue appears to occur when running import('some-module').SomeType within a type definition in svelte code.

Link to GitHub Repo with Minimal Reproducible Example

https://github.com/MaddyGuthridge/eslint-plugin-svelte-repro

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

  • 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?

9.39.1

What version of eslint-plugin-svelte are you using?

3.13.0

What did you do?

Updated typescript-eslint from 8.47.0 to 8.48.0

Configuration
// @ts-check
import js from '@eslint/js';
import ts from 'typescript-eslint';
import svelte from 'eslint-plugin-svelte';
import svelteParser from 'svelte-eslint-parser';
import stylistic from '@stylistic/eslint-plugin';
import esNode from 'eslint-plugin-n';
import pluginQuery from '@tanstack/eslint-plugin-query'
import globals from 'globals';

export default ts.config(
  js.configs.recommended,
  ...ts.configs.recommendedTypeChecked,
  ...ts.configs.stylisticTypeChecked,
  ...svelte.configs['flat/recommended'],
  stylistic.configs.recommended,
  ...pluginQuery.configs['flat/recommended'],
  {
    plugins: {
      n: esNode,
    },
  },
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
        // Seemingly not defined in `globals.browser`
        ScrollBehavior: 'readonly',
      }
    }
  },
  {
    languageOptions: {
      parser: ts.parser,
      parserOptions: {
        // ...
        project: true,
        extraFileExtensions: ['.svelte'], // This is a required setting in `@typescript-eslint/parser` v4.24.0.
      },
    },
  },
  {
    files: ['**/*.svelte', '*.svelte'],
    languageOptions: {
      parser: svelteParser,
      // Parse the `<script>` in `.svelte` as TypeScript by adding the following configuration.
      parserOptions: {
        parser: ts.parser,
      },
    },
  },
  {
    rules: {
      // Default ESLint rules
      // ====================

      // Use `Promise.all` instead of `await` in a for loop for better async performance
      'no-await-in-loop': 'error',
      // Don't allow duplicate imports, because they are yucky
      'no-duplicate-imports': 'error',
      // Common mistake with `new Promise`
      'no-promise-executor-return': ['error', { allowVoid: true }],
      // Accidentally forgetting to use `back-ticks` for template literals
      'no-template-curly-in-string': 'error',
      // Use === instead of ==
      'eqeqeq': 'error',
      // Use dot notation for object property access
      'dot-notation': 'error',
      // Don't use `alert` and similar functions
      'no-alert': 'error',
      // Use camelCase for naming
      'camelcase': 'error',
      // Use `const` over `let` where reasonable
      // Not required for destructuring, since that just makes things painful for Svelte props where
      // some props are bindable
      'prefer-const': ['error', { destructuring: 'all' }],

      'no-restricted-imports': ['error', {
        patterns: [
          // Don't use legacy svelte features
          { group: ['svelte/legacy'], message: 'Avoid legacy Svelte features' },
          // Prevent importing from test files -- it causes tests to be registered multiple times,
          // making pipelines slow
          {
            regex: '.*\\.test(.(t|j)s)?$',
            message: (
              'Do not import from test files. This causes tests to be run repeatedly. Instead, '
              + 'move shared functionality to a separate non-test file.'),
          },
        ]
      }],

      // @typescript-eslint rules
      // ========================

      // Allow explicit any, to avoid type gymnastics
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
        caughtErrors: 'none',
      }],
      // Disallow floating promises to avoid random crashes
      '@typescript-eslint/no-floating-promises': 'error',
      // Allow some `any` expressions since otherwise they seriously mess with tests, or enforce
      // strictness in areas where it really doesn't matter (eg error handling)
      '@typescript-eslint/no-unsafe-assignment': 'off',
      '@typescript-eslint/no-unsafe-argument': 'off',
      '@typescript-eslint/no-unsafe-return': 'off',
      // Also disable template expression checks, since they're also error handling stuff
      // TODO: Enable them at some point when I get around to actually tidying things up
      '@typescript-eslint/no-base-to-string': 'off',
      '@typescript-eslint/restrict-template-expressions': 'off',
      // FIXME: When I get around to hardening the request body validation, enable this rule again
      '@typescript-eslint/no-unsafe-member-access': 'off',
      // Allow empty functions, as they are useful to silence promise errors
      '@typescript-eslint/no-empty-function': 'off',
      // Use `type` instead of `interface`
      '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
      // This error is already picked up by TypeScript, and it's annoying to need to silence it
      // twice when it is incorrect
      '@typescript-eslint/no-unsafe-call': 'off',

      // Stylistic ESLint rules
      // ======================

      // Use semicolons to help prevent weird and wonderful JS quirks
      '@stylistic/semi': ['error', 'always', { omitLastInOneLineBlock: true }],
      // Single quotes where possible
      '@stylistic/quotes': ['error', 'single', { avoidEscape: true, allowTemplateLiterals: 'never' }],
      // Only quote object properties if it'd be a syntax error or bad style otherwise
      '@stylistic/quote-props': ['error', 'consistent'],
      // Use one true brace style
      '@stylistic/brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
      // Always use comma for delimiting type definitions, since it matches object notation
      '@stylistic/member-delimiter-style': ['error', {
        multiline: {
          delimiter: 'comma',
          requireLast: true,
        },
        singleline: {
          delimiter: 'comma',
          requireLast: false,
        }
      }],

      // Node (eslint-plugin-n) rules
      // ============================

      // For bin scripts listed in package.json, require a correct shebang
      'n/hashbang': 'error',
      // Don't concat to __file and __dirname (this is unsafe, use `path.join` instead)
      'n/no-path-concat': 'error',
      // Use `node:` prefix when importing from node standard library modules (they don't exist in
      // some other runtimes)
      'n/prefer-node-protocol': 'error',

      // Svelte (eslint-plugin-svelte) rules
      // ===================================

      'svelte/indent': ['error', { indent: 2 }],
      'svelte/prefer-class-directive': ['error', {'prefer': 'empty'}],
      'svelte/prefer-style-directive': 'error',
      'svelte/spaced-html-comment': 'error',
      // Disable no-navigation-without-resolve for the time being. I want to resolve these issues
      // eventually, but for now, it is too much effort.
      // TODO: Fix this in the future.
      'svelte/no-navigation-without-resolve': 'off',
      // Allow comments inside of mustaches in svelte syntax
      "svelte/no-useless-mustaches": ["error", { "ignoreIncludesComment": true, "ignoreStringEscape": true }],
    },
  },
  {
    ignores: [
      '**/.DS_Store',
      '**/node_modules',
      'build',
      '.svelte-kit',
      'package',
      '**/.env',
      '**/.env.*',
      '!**/.env.example',
      '**/pnpm-lock.yaml',
      '**/package-lock.json',
      '**/yarn.lock',
      '**/svelte.config.js',
      '**/vitest.config.ts',
      'eslint.config.mjs',
      'vite.config.ts',
    ],
  },
);
<script lang="ts">
  import Button from './Button.svelte';

  type Props = {
    text: string,
    hint?: string,
    children?: import('svelte').Snippet,
  };

  const { text, hint = 'Copy', children }: Props = $props();

  function copy() {
    void navigator.clipboard.writeText(text);
  }
</script>

<Button onclick={copy} {hint}>
  <div class="copy-btn">
    {@render children?.()}
  </div>
</Button>

<style>
  .copy-btn {
    display: flex;
    align-items: center;
    gap: 5px;
  }
</style>

What did you expect to happen?

ESLint executes normally, reporting no errors.

What actually happened?

ESLint crashed while running the rule svelte/indent

| maddy@kronk :: ~/Source/Minifolio (dependabot/npm_and_yarn/eslint-8c65fd12c5) ⚡
⋙   npm ls eslint-plugin-svelte eslint typescript-eslint
Details
[email protected] /home/maddy/Source/Minifolio
├─┬ @stylistic/[email protected]
│ ├─┬ @eslint-community/[email protected]
│ │ └── [email protected] deduped
│ └── [email protected] deduped
├─┬ @tanstack/[email protected]
│ ├─┬ @typescript-eslint/[email protected]
│ │ └── [email protected] deduped
│ └── [email protected] deduped
├─┬ [email protected]
│ ├─┬ [email protected]
│ │ ├─┬ [email protected]
│ │ │ └── [email protected] deduped
│ │ └── [email protected] deduped
│ └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
├── [email protected]
└─┬ [email protected]
  ├─┬ @typescript-eslint/[email protected]
  │ ├─┬ @typescript-eslint/[email protected]
  │ │ ├─┬ @typescript-eslint/[email protected]
  │ │ │ └── [email protected] deduped
  │ │ └── [email protected] deduped
  │ ├─┬ @typescript-eslint/[email protected]
  │ │ └── [email protected] deduped
  │ └── [email protected] deduped
  ├─┬ @typescript-eslint/[email protected]
  │ └── [email protected] deduped
  ├─┬ @typescript-eslint/[email protected]
  │ └── [email protected] deduped
  └── [email protected] deduped
| maddy@kronk :: ~/Source/Minifolio (dependabot/npm_and_yarn/eslint-8c65fd12c5) ⚡
⋙   npm run lint

> [email protected] lint
> svelte-kit sync && eslint .

| maddy@kronk :: ~/Source/Minifolio (dependabot/npm_and_yarn/eslint-8c65fd12c5) ⚡
⋙   npm i [email protected]

removed 35 packages, changed 4 packages, and audited 532 packages in 4s

120 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
| maddy@kronk :: ~/Source/Minifolio (dependabot/npm_and_yarn/eslint-8c65fd12c5) ⚡
⋙   npm run lint
> [email protected] lint
> svelte-kit sync && eslint .


Oops! Something went wrong! :(

ESLint: 9.39.1

TypeError: Cannot read properties of null (reading 'range')
Occurred while linting /home/maddy/Source/Minifolio/src/components/base/button/CopyButton.svelte:7
Rule: "svelte/indent"
    at SourceCode.getTokenBefore (/home/maddy/Source/Minifolio/node_modules/eslint/lib/languages/js/source-code/token-store/index.js:386:9)
    at getFirstAndLastTokens (file:///home/maddy/Source/Minifolio/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/commons.js:13:31)
    at OffsetContext._setOffsetElementList (file:///home/maddy/Source/Minifolio/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:117:35)
    at OffsetContext.setOffsetElementList (file:///home/maddy/Source/Minifolio/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:108:14)
    at TSImportType (file:///home/maddy/Source/Minifolio/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/ts.js:552:21)
    at ruleErrorHandler (/home/maddy/Source/Minifolio/node_modules/eslint/lib/linter/linter.js:1173:33)
    at /home/maddy/Source/Minifolio/node_modules/eslint/lib/linter/source-code-visitor.js:76:46
    at Array.forEach (<anonymous>)
    at SourceCodeVisitor.callSync (/home/maddy/Source/Minifolio/node_modules/eslint/lib/linter/source-code-visitor.js:76:30)
    at /home/maddy/Source/Minifolio/node_modules/eslint/lib/linter/source-code-traverser.js:291:18

Occasionally the error is:

| maddy@kronk :: ~/Source/other-peoples-problems/eslint-plugin-svelte-repro (main)
   npm run lint                                                                              

> eslint-plugin-svelte-repro@0.0.1 lint
> eslint .


Oops! Something went wrong! :(

ESLint: 9.39.1

RangeError: Maximum call stack size exceeded
Occurred while linting /home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/src/routes/Container.svelte:1
Rule: "svelte/indent"
    at Map.get (<anonymous>)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:169:41)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:179:33)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:179:33)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:179:33)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:179:33)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:179:33)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:179:33)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:179:33)
    at OffsetCalculator.getExpectedIndentFromIndex (file:///home/maddy/Source/other-peoples-problems/eslint-plugin-svelte-repro/node_modules/eslint-plugin-svelte/lib/rules/indent-helpers/offset-context.js:179:33)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions