- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 59
Open
Description
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.36.0
What version of eslint-plugin-svelte are you using?
3.12.4
What did you do?
Configuration from README:
Configuration
import js from "@eslint/js";
import svelte from "eslint-plugin-svelte";
import globals from "globals";
import ts from "typescript-eslint";
import svelteConfig from "./svelte.config.js";
export default ts.config(
    js.configs.recommended,
    ...ts.configs.recommended,
    ...svelte.configs.recommended,
    {
        languageOptions: {
            globals: {
                ...globals.browser,
                ...globals.node,
            },
        },
    },
    {
        files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
        // See more details at: https://typescript-eslint.io/packages/parser/
        languageOptions: {
            parserOptions: {
                projectService: true,
                extraFileExtensions: [".svelte"], // Add support for additional file extensions, such as .svelte
                parser: ts.parser,
                // Specify a parser for each language, if needed:
                // parser: {
                //   ts: ts.parser,
                //   js: espree,    // Use espree for .js files (add: import espree from 'espree')
                //   typescript: ts.parser
                // },
                // We recommend importing and specifying svelte.config.js.
                // By doing so, some rules in eslint-plugin-svelte will automatically read the configuration and adjust their behavior accordingly.
                // While certain Svelte settings may be statically loaded from svelte.config.js even if you don’t specify it,
                // explicitly specifying it ensures better compatibility and functionality.
                //
                // If non-serializable properties are included, running ESLint with the --cache flag will fail.
                // In that case, please remove the non-serializable properties. (e.g. `svelteConfig: { ...svelteConfig, kit: { ...svelteConfig.kit, typescript: undefined }}`)
                svelteConfig,
            },
        },
    },
    {
        rules: {
            // Override or add rule settings here, such as:
            // 'svelte/rule-name': 'error'
        },
    }
);
Below svelte code copied verbatim from shadcn-svelte data table component.
import {
    type RowData,
    type TableOptions,
    type TableOptionsResolved,
    type TableState,
    createTable,
} from "@tanstack/table-core";
/**
 * Creates a reactive TanStack table object for Svelte.
 * @param options Table options to create the table with.
 * @returns A reactive table object.
 * @example
 * ```svelte
 * <script>
 *   const table = createSvelteTable({ ... })
 * </script>
 *
 * <table>
 *   <thead>
 *     {#each table.getHeaderGroups() as headerGroup}
 *       <tr>
 *         {#each headerGroup.headers as header}
 *           <th colspan={header.colSpan}>
 *         	   <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
 *         	 </th>
 *         {/each}
 *       </tr>
 *     {/each}
 *   </thead>
 * 	 <!-- ... -->
 * </table>
 * ```
 */
export function createSvelteTable<TData extends RowData>(options: TableOptions<TData>) {
    const resolvedOptions: TableOptionsResolved<TData> = mergeObjects(
        {
            state: {},
            onStateChange() {},
            renderFallbackValue: null,
            mergeOptions: (
                defaultOptions: TableOptions<TData>,
                options: Partial<TableOptions<TData>>
            ) => {
                return mergeObjects(defaultOptions, options);
            },
        },
        options
    );
    const table = createTable(resolvedOptions);
    let state = $state<Partial<TableState>>(table.initialState);
    function updateOptions() {
        table.setOptions((prev) => {
            return mergeObjects(prev, options, {
                state: mergeObjects(state, options.state || {}),
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                onStateChange: (updater: any) => {
                    if (updater instanceof Function) state = updater(state);
                    else state = mergeObjects(state, updater);
                    options.onStateChange?.(updater);
                },
            });
        });
    }
    updateOptions();
    $effect.pre(() => {
        updateOptions();
    });
    return table;
}
type MaybeThunk<T extends object> = T | (() => T | null | undefined);
type Intersection<T extends readonly unknown[]> = (T extends [infer H, ...infer R]
    ? H & Intersection<R>
    : unknown) & {};
/**
 * Lazily merges several objects (or thunks) while preserving
 * getter semantics from every source.
 *
 * Proxy-based to avoid known WebKit recursion issue.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function mergeObjects<Sources extends readonly MaybeThunk<any>[]>(
    ...sources: Sources
): Intersection<{ [K in keyof Sources]: Sources[K] }> {
    const resolve = <T extends object>(src: MaybeThunk<T>): T | undefined =>
        typeof src === "function" ? (src() ?? undefined) : src;
    const findSourceWithKey = (key: PropertyKey) => {
        for (let i = sources.length - 1; i >= 0; i--) {
            const obj = resolve(sources[i]);
            if (obj && key in obj) return obj;
        }
        return undefined;
    };
    return new Proxy(Object.create(null), {
        get(_, key) {
            const src = findSourceWithKey(key);
            return src?.[key as never];
        },
        has(_, key) {
            return !!findSourceWithKey(key);
        },
        ownKeys(): (string | symbol)[] {
            const all = new Set<string | symbol>();
            for (const s of sources) {
                const obj = resolve(s);
                if (obj) {
                    for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) {
                        all.add(k);
                    }
                }
            }
            return [...all];
        },
        getOwnPropertyDescriptor(_, key) {
            const src = findSourceWithKey(key);
            if (!src) return undefined;
            return {
                configurable: true,
                enumerable: true,
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                value: (src as any)[key],
                writable: true,
            };
        },
    }) as Intersection<{ [K in keyof Sources]: Sources[K] }>;
}What did you expect to happen?
svelte rules should trigger once only for svelte.ts files
What actually happened?
svelte rules trigger twice:
% npm run lint
> my-app@0.0.1 lint
> prettier --check . && eslint .
Checking formatting...
All matched files use Prettier code style!
...
/.../src/lib/components/ui/data-table/data-table.svelte.ts
  117:25  error  Found a mutable instance of the built-in Set class. Use SvelteSet instead  svelte/prefer-svelte-reactivity
  117:25  error  Found a mutable instance of the built-in Set class. Use SvelteSet instead  svelte/prefer-svelte-reactivity
✖ 19 problems (19 errors, 0 warnings)
This is on a fresh install where we installed shadcn-svelte to verify
Link to GitHub Repo with Minimal Reproducible Example
eslint online playground does not seem to work. Repro steps:
- initialize a svelte project
- add the above file
- run npm run lint
- see duplicate eslint errors
Additional comments
No response
Metadata
Metadata
Assignees
Labels
No labels