diff --git a/.github/workflows/pkg.pr.new-comment.yml b/.github/workflows/pkg.pr.new-comment.yml index 788908ef..5724d648 100644 --- a/.github/workflows/pkg.pr.new-comment.yml +++ b/.github/workflows/pkg.pr.new-comment.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: output github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index ce969872..21cad427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte-eslint-parser +## 1.3.1 + +### Patch Changes + +- [#732](https://github.com/sveltejs/svelte-eslint-parser/pull/732) [`1350734`](https://github.com/sveltejs/svelte-eslint-parser/commit/1350734793ad8cf86b660aa8e3337be3c48fb5d4) Thanks [@ota-meshi](https://github.com/ota-meshi)! - fix: `$derived` argument expression to apply correct type information to `this` + ## 1.3.0 ### Minor Changes diff --git a/explorer-v2/package.json b/explorer-v2/package.json index 74e4f661..d961b3af 100644 --- a/explorer-v2/package.json +++ b/explorer-v2/package.json @@ -21,18 +21,18 @@ "svelte": "^5.2.12", "svelte-eslint-parser": "link:..", "tslib": "^2.8.1", - "vite-plugin-eslint4b": "^0.5.1" + "vite-plugin-eslint4b": "^0.6.0" }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.9.0", - "@sveltejs/vite-plugin-svelte": "^5.0.1", + "@sveltejs/vite-plugin-svelte": "^6.0.0", "monaco-editor": "^0.52.0", "prettier": "^3.4.1", "prettier-plugin-svelte": "^3.3.2", "string-replace-loader": "^3.1.0", "typescript": "^5.7.2", - "vite": "^6.0.1", + "vite": "^7.0.0", "webpack": "^5.96.1", "webpack-cli": "^6.0.0", "wrapper-webpack-plugin": "^2.2.2" diff --git a/explorer-v2/src/lib/ESLintEditor.svelte b/explorer-v2/src/lib/ESLintEditor.svelte index cc440f2c..8537401c 100644 --- a/explorer-v2/src/lib/ESLintEditor.svelte +++ b/explorer-v2/src/lib/ESLintEditor.svelte @@ -3,6 +3,7 @@ import MonacoEditor from './MonacoEditor.svelte'; import { loadMonacoEditor } from './scripts/monaco-loader'; import { createEventDispatcher, onMount } from 'svelte'; + import { SvelteMap } from 'svelte/reactivity'; const dispatch = createEventDispatcher(); @@ -16,7 +17,7 @@ let leftMarkers = []; let rightMarkers = []; - let messageMap = new Map(); + let messageMap = new SvelteMap(); $: showApplyFix = fix && fixedValue !== code; $: { diff --git a/package.json b/package.json index f5d91d48..2898778e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte-eslint-parser", - "version": "1.3.0", + "version": "1.3.1", "type": "module", "description": "Svelte parser for ESLint", "repository": "git+https://github.com/sveltejs/svelte-eslint-parser.git", @@ -66,7 +66,7 @@ "@changesets/changelog-github": "^0.5.1", "@changesets/cli": "^2.28.1", "@changesets/get-release-plan": "^4.0.8", - "@ota-meshi/eslint-plugin": "^0.17.6", + "@ota-meshi/eslint-plugin": "^0.18.0", "@ota-meshi/test-snapshot": "^1.1.0", "@types/benchmark": "^2.1.5", "@types/chai": "^5.2.1", @@ -84,10 +84,10 @@ "chai": "^5.2.0", "env-cmd": "^10.1.0", "esbuild": "^0.25.1", - "eslint": "~9.27.0", + "eslint": "~9.33.0", "eslint-config-prettier": "^10.1.1", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-jsdoc": "^50.6.9", + "eslint-plugin-jsdoc": "^54.0.0", "eslint-plugin-json-schema-validator": "^5.3.1", "eslint-plugin-jsonc": "^2.20.0", "eslint-plugin-n": "^17.17.0", @@ -100,15 +100,15 @@ "locate-character": "^3.0.0", "magic-string": "^0.30.17", "mocha": "^11.1.0", - "prettier": "~3.5.3", - "prettier-plugin-pkg": "^0.19.0", + "prettier": "~3.6.0", + "prettier-plugin-pkg": "^0.21.0", "prettier-plugin-svelte": "^3.3.3", "rimraf": "^6.0.1", "semver": "^7.7.1", "svelte": "^5.36.2", "svelte2tsx": "^0.7.35", "tsx": "^4.19.3", - "typescript": "~5.8.2", + "typescript": "~5.9.0", "typescript-eslint": "^8.28.0", "typescript-eslint-parser-for-extra-files": "^0.9.0" }, diff --git a/src/meta.ts b/src/meta.ts index 3a3f5c18..023b70e8 100644 --- a/src/meta.ts +++ b/src/meta.ts @@ -2,4 +2,4 @@ // This file has been automatically generated, // in order to update its content execute "pnpm run build:meta" export const name = "svelte-eslint-parser" as const; -export const version = "1.3.0" as const; +export const version = "1.3.1" as const; diff --git a/src/parser/typescript/analyze/index.ts b/src/parser/typescript/analyze/index.ts index 4dde5837..ed13f0d7 100644 --- a/src/parser/typescript/analyze/index.ts +++ b/src/parser/typescript/analyze/index.ts @@ -946,17 +946,18 @@ function transformForReactiveStatement( } /** - * Transform for `$derived(expr)` to `$derived((()=>{ return fn(); function fn () { return expr } })())` + * Transform for `$derived(expr)` to `$derived((()=>{ type This = typeof this; return fn(); function fn (this: This) { return expr } })())` */ function transformForDollarDerived( derivedCall: TSESTree.CallExpression, ctx: VirtualTypeScriptContext, ) { const functionId = ctx.generateUniqueId("$derivedArgument"); + const thisTypeId = ctx.generateUniqueId("$This"); const expression = derivedCall.arguments[0]; ctx.appendOriginal(expression.range[0]); ctx.appendVirtualScript( - `(()=>{return ${functionId}();function ${functionId}(){return `, + `(()=>{type ${thisTypeId} = typeof this; return ${functionId}();function ${functionId}(this: ${thisTypeId}){return `, ); ctx.appendOriginal(expression.range[1]); ctx.appendVirtualScript(`}})()`); @@ -977,21 +978,40 @@ function transformForDollarDerived( arg.arguments.length !== 0 || arg.callee.type !== "ArrowFunctionExpression" || arg.callee.body.type !== "BlockStatement" || - arg.callee.body.body.length !== 2 || - arg.callee.body.body[0].type !== "ReturnStatement" || - arg.callee.body.body[0].argument?.type !== "CallExpression" || - arg.callee.body.body[0].argument.callee.type !== "Identifier" || - arg.callee.body.body[0].argument.callee.name !== functionId || - arg.callee.body.body[1].type !== "FunctionDeclaration" || - arg.callee.body.body[1].id.name !== functionId + arg.callee.body.body.length !== 3 ) { return false; } - const fnNode = arg.callee.body.body[1]; + const thisTypeNode = arg.callee.body.body[0]; if ( + thisTypeNode.type !== "TSTypeAliasDeclaration" || + thisTypeNode.id.name !== thisTypeId + ) { + return false; + } + const returnNode = arg.callee.body.body[1]; + if ( + returnNode.type !== "ReturnStatement" || + returnNode.argument?.type !== "CallExpression" || + returnNode.argument.callee.type !== "Identifier" || + returnNode.argument.callee.name !== functionId + ) { + return false; + } + + const fnNode = arg.callee.body.body[2]; + if ( + fnNode.type !== "FunctionDeclaration" || + fnNode.id.name !== functionId || fnNode.body.body.length !== 1 || fnNode.body.body[0].type !== "ReturnStatement" || - !fnNode.body.body[0].argument + !fnNode.body.body[0].argument || + fnNode.params[0]?.type !== "Identifier" || + !fnNode.params[0].typeAnnotation || + fnNode.params[0].typeAnnotation.typeAnnotation.type !== + "TSTypeReference" || + fnNode.params[0].typeAnnotation.typeAnnotation.typeName.type !== + "Identifier" ) { return false; } @@ -1002,11 +1022,16 @@ function transformForDollarDerived( expr.parent = node; const scopeManager = result.scopeManager as ScopeManager; - removeFunctionScope(arg.callee.body.body[1], scopeManager); + const fnScope = scopeManager.acquire(fnNode)!; + removeIdentifierVariable(fnNode.params[0], fnScope); removeIdentifierReference( - arg.callee.body.body[0].argument.callee, - scopeManager.acquire(arg.callee)!, + fnNode.params[0].typeAnnotation.typeAnnotation.typeName, + fnScope, ); + removeFunctionScope(fnNode, scopeManager); + const scope = scopeManager.acquire(arg.callee)!; + removeIdentifierVariable(thisTypeNode.id, scope); + removeIdentifierReference(returnNode.argument.callee, scope); removeFunctionScope(arg.callee, scopeManager); return true; }, diff --git a/tests/fixtures/integrations/type-info-tests/$derived-within-class-input.svelte.ts b/tests/fixtures/integrations/type-info-tests/$derived-within-class-input.svelte.ts new file mode 100644 index 00000000..ff8e8c03 --- /dev/null +++ b/tests/fixtures/integrations/type-info-tests/$derived-within-class-input.svelte.ts @@ -0,0 +1,5 @@ +export class Product { + x = $state(1) + y = $state(2) + result = $derived(this.x * this.y) +} \ No newline at end of file diff --git a/tests/fixtures/integrations/type-info-tests/$derived-within-class-output.json b/tests/fixtures/integrations/type-info-tests/$derived-within-class-output.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/tests/fixtures/integrations/type-info-tests/$derived-within-class-output.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/integrations/type-info-tests/$derived-within-class-requirements.json b/tests/fixtures/integrations/type-info-tests/$derived-within-class-requirements.json new file mode 100644 index 00000000..809a4e1f --- /dev/null +++ b/tests/fixtures/integrations/type-info-tests/$derived-within-class-requirements.json @@ -0,0 +1,5 @@ +{ + "parse": { + "svelte": ">=5.0.0-0" + } +} \ No newline at end of file diff --git a/tests/fixtures/integrations/type-info-tests/$derived-within-class-setup.ts b/tests/fixtures/integrations/type-info-tests/$derived-within-class-setup.ts new file mode 100644 index 00000000..af932ff3 --- /dev/null +++ b/tests/fixtures/integrations/type-info-tests/$derived-within-class-setup.ts @@ -0,0 +1,33 @@ +import type { Linter } from "eslint"; +import { generateParserOptions } from "../../../src/parser/test-utils.js"; +import { plugin } from "typescript-eslint"; +import * as parser from "../../../../src/index.js"; +import globals from "globals"; + +export function getConfig(): Linter.Config { + return { + plugins: { + "@typescript-eslint": { + rules: plugin.rules as any, + }, + }, + languageOptions: { + parser, + parserOptions: { + ...generateParserOptions(), + svelteFeatures: { runes: true }, + }, + globals: { + ...globals.browser, + ...globals.es2021, + }, + }, + rules: { + "@typescript-eslint/no-unsafe-argument": "error", + "@typescript-eslint/no-unsafe-assignment": "error", + "@typescript-eslint/no-unsafe-call": "error", + "@typescript-eslint/no-unsafe-member-access": "error", + "@typescript-eslint/no-unsafe-return": "error", + }, + }; +} diff --git a/tests/fixtures/parser/ast/svelte5/docs/runes/11-2-$inspect-ts-output.json b/tests/fixtures/parser/ast/svelte5/docs/runes/11-2-$inspect-ts-output.json index 0b52ca4c..69669535 100644 --- a/tests/fixtures/parser/ast/svelte5/docs/runes/11-2-$inspect-ts-output.json +++ b/tests/fixtures/parser/ast/svelte5/docs/runes/11-2-$inspect-ts-output.json @@ -1188,7 +1188,7 @@ } }, { - "type": "Keyword", + "type": "Identifier", "value": "with", "range": [ 63, diff --git a/tests/fixtures/parser/ast/svelte5/docs/runes/11-3-$inspect-ts-output.json b/tests/fixtures/parser/ast/svelte5/docs/runes/11-3-$inspect-ts-output.json index 3a146be6..e247f41f 100644 --- a/tests/fixtures/parser/ast/svelte5/docs/runes/11-3-$inspect-ts-output.json +++ b/tests/fixtures/parser/ast/svelte5/docs/runes/11-3-$inspect-ts-output.json @@ -811,7 +811,7 @@ } }, { - "type": "Keyword", + "type": "Identifier", "value": "with", "range": [ 63, diff --git a/tests/fixtures/parser/ast/ts-await-non-promise01-output.json b/tests/fixtures/parser/ast/ts-await-non-promise01-output.json index 1600d867..3ff706ee 100644 --- a/tests/fixtures/parser/ast/ts-await-non-promise01-output.json +++ b/tests/fixtures/parser/ast/ts-await-non-promise01-output.json @@ -1347,7 +1347,7 @@ } }, { - "type": "Keyword", + "type": "Identifier", "value": "const", "range": [ 41, diff --git a/tests/fixtures/parser/ast/ts-issue226-output.json b/tests/fixtures/parser/ast/ts-issue226-output.json index f037ef8f..40511c22 100644 --- a/tests/fixtures/parser/ast/ts-issue226-output.json +++ b/tests/fixtures/parser/ast/ts-issue226-output.json @@ -2909,7 +2909,7 @@ } }, { - "type": "Keyword", + "type": "Identifier", "value": "catch", "range": [ 210,