From 45ad73b5ac7b77a42867be07bef94d1c1d825d12 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Thu, 27 Oct 2022 21:32:25 +0900 Subject: [PATCH 1/2] fix: crash in `@typescript-eslint/no-misused-promises` rule --- docs/internal-mechanism.md | 20 +++-- src/parser/typescript/analyze/index.ts | 82 ++++++++++++------- .../ts-no-misused-promises-input.svelte | 5 ++ .../ts-no-misused-promises-output.json | 1 + .../ts-no-misused-promises-setup.ts | 24 ++++++ 5 files changed, 97 insertions(+), 35 deletions(-) create mode 100644 tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-input.svelte create mode 100644 tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-output.json create mode 100644 tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-setup.ts diff --git a/docs/internal-mechanism.md b/docs/internal-mechanism.md index 22fe7b5d..24cbed43 100644 --- a/docs/internal-mechanism.md +++ b/docs/internal-mechanism.md @@ -157,14 +157,22 @@ Parse the following virtual script code as a script: export let foo: { bar: number } | null = null -$: function $_reactiveStatementScopeFunction1(){console.log(foo && foo.bar);} +$: function $_reactiveStatementScopeFunction1(){ + console.log(foo && foo.bar); +} -$: let r = $_reactiveVariableScopeFunction2(); -function $_reactiveVariableScopeFunction2(){return foo && foo.bar;} +$: let r =$_reactiveVariableScopeFunction2(); +function $_reactiveVariableScopeFunction2(){ + let $_tmpVar3; + return ($_tmpVar3 = foo && foo.bar); +} -$: let { bar: n } = $_reactiveVariableScopeFunction3(); -function $_reactiveVariableScopeFunction3(){return foo || { bar: 42 };} -;function $_render4(){ +$: let { bar: n } =$_reactiveVariableScopeFunction4(); +function $_reactiveVariableScopeFunction4(){ + let $_tmpVar5; + return ($_tmpVar5 = foo || { bar: 42 }); +} +;function $_render6(){ (foo && foo.bar); } diff --git a/src/parser/typescript/analyze/index.ts b/src/parser/typescript/analyze/index.ts index 3ae04e07..08fb7d0d 100644 --- a/src/parser/typescript/analyze/index.ts +++ b/src/parser/typescript/analyze/index.ts @@ -231,7 +231,7 @@ function transformForDeclareReactiveVar( // // To: // $: let id = fn() - // function fn () { return x + y; } + // function fn () { let tmp; return (tmp = x + y); } // // // From: @@ -239,7 +239,7 @@ function transformForDeclareReactiveVar( // // To: // $: let {id} = fn() - // function fn () { return foo; } + // function fn () { let tmp; return (tmp = foo); } /** * The opening paren tokens for @@ -297,6 +297,7 @@ function transformForDeclareReactiveVar( } const functionId = ctx.generateUniqueId("reactiveVariableScopeFunction"); + const tmpVarId = ctx.generateUniqueId("tmpVar"); for (const token of openParens) { ctx.appendOriginal(token.range[0]); ctx.skipOriginalOffset(token.range[1] - token.range[0]); @@ -306,7 +307,7 @@ function transformForDeclareReactiveVar( ctx.appendVirtualScript("let "); ctx.appendOriginal(eq ? eq.range[1] : expression.right.range[0]); ctx.appendVirtualScript( - `${functionId}();\nfunction ${functionId}(){return (` + `${functionId}();\nfunction ${functionId}(){let ${tmpVarId};return (${tmpVarId} = ` ); ctx.appendOriginal(expression.right.range[1]); ctx.appendVirtualScript(`)`); @@ -347,46 +348,61 @@ function transformForDeclareReactiveVar( !fnDecl || fnDecl.type !== "FunctionDeclaration" || fnDecl.id.name !== functionId || - fnDecl.body.body.length !== 1 || - fnDecl.body.body[0].type !== "ReturnStatement" + fnDecl.body.body.length !== 2 || + fnDecl.body.body[0].type !== "VariableDeclaration" || + fnDecl.body.body[1].type !== "ReturnStatement" ) { return false; } - const returnStatement = fnDecl.body.body[0]; - if (returnStatement.argument?.type !== expression.right.type) { + const tmpVarDeclaration = fnDecl.body.body[0]; + if ( + tmpVarDeclaration.declarations.length !== 1 || + tmpVarDeclaration.declarations[0].type !== "VariableDeclarator" + ) { return false; } + const tempVarDeclId = tmpVarDeclaration.declarations[0].id; + if ( + tempVarDeclId.type !== "Identifier" || + tempVarDeclId.name !== tmpVarId + ) { + return false; + } + const returnStatement = fnDecl.body.body[1]; + const assignment = returnStatement.argument; + if ( + assignment?.type !== "AssignmentExpression" || + assignment.left.type !== "Identifier" || + assignment.right.type !== expression.right.type + ) { + return false; + } + const tempLeft = assignment.left; // Remove function declaration program.body.splice(nextIndex, 1); // Restore expression statement - const newExpression: TSESTree.AssignmentExpression = { - type: "AssignmentExpression" as TSESTree.AssignmentExpression["type"], - operator: "=", - left: idDecl.id, - right: returnStatement.argument, - loc: { - start: idDecl.id.loc.start, - end: expressionCloseParen - ? expressionCloseParen.loc.end - : returnStatement.argument.loc.end, - }, - range: [ - idDecl.id.range[0], - expressionCloseParen - ? expressionCloseParen.range[1] - : returnStatement.argument.range[1], - ], + assignment.left = idDecl.id; + assignment.loc = { + start: idDecl.id.loc.start, + end: expressionCloseParen + ? expressionCloseParen.loc.end + : assignment.right.loc.end, }; - idDecl.id.parent = newExpression; - returnStatement.argument.parent = newExpression; + assignment.range = [ + idDecl.id.range[0], + expressionCloseParen + ? expressionCloseParen.range[1] + : assignment.right.range[1], + ]; + idDecl.id.parent = assignment; const newBody: TSESTree.ExpressionStatement = { type: "ExpressionStatement" as TSESTree.ExpressionStatement["type"], - expression: newExpression, + expression: assignment, loc: statement.body.loc, range: statement.body.range, parent: reactiveStatement, }; - newExpression.parent = newBody; + assignment.parent = newBody; reactiveStatement.body = newBody; // Restore statement end location reactiveStatement.range[1] = returnStatement.range[1]; @@ -401,12 +417,20 @@ function transformForDeclareReactiveVar( ); const scopeManager = result.scopeManager as ScopeManager; + removeAllScopeAndVariableAndReference(tmpVarDeclaration, { + visitorKeys: result.visitorKeys, + scopeManager, + }); removeFunctionScope(fnDecl, scopeManager); + const scope = getProgramScope(scopeManager); for (const reference of getAllReferences(idDecl.id, scope)) { - reference.writeExpr = newExpression.right as ESTree.Expression; + reference.writeExpr = assignment.right as ESTree.Expression; } + removeIdentifierReference(tempLeft, scope); + removeIdentifierVariable(tempVarDeclId, scope); + removeIdentifierReference(idDecl.init.callee, scope); removeIdentifierVariable(idDecl.id, scope); return true; diff --git a/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-input.svelte b/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-input.svelte new file mode 100644 index 00000000..6eb60359 --- /dev/null +++ b/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-input.svelte @@ -0,0 +1,5 @@ + + +{noMisusedPromisesvar} diff --git a/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-output.json b/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-output.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-output.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-setup.ts b/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-setup.ts new file mode 100644 index 00000000..4e700d7f --- /dev/null +++ b/tests/fixtures/integrations/type-info-tests/ts-no-misused-promises-setup.ts @@ -0,0 +1,24 @@ +/* eslint eslint-comments/require-description: 0, @typescript-eslint/explicit-module-boundary-types: 0 */ +import type { Linter } from "eslint"; +import { BASIC_PARSER_OPTIONS } from "../../../src/parser/test-utils"; +import { rules } from "@typescript-eslint/eslint-plugin"; +export function setupLinter(linter: Linter) { + linter.defineRule( + "@typescript-eslint/no-misused-promises", + rules["no-misused-promises"] as never + ); +} + +export function getConfig() { + return { + parser: "svelte-eslint-parser", + parserOptions: BASIC_PARSER_OPTIONS, + rules: { + "@typescript-eslint/no-misused-promises": "error", + }, + env: { + browser: true, + es2021: true, + }, + }; +} From 1cb4ca0a0ca0fc9ecbc76429852bfac91f14671a Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 27 Oct 2022 21:33:24 +0900 Subject: [PATCH 2/2] Create ninety-cheetahs-remain.md --- .changeset/ninety-cheetahs-remain.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/ninety-cheetahs-remain.md diff --git a/.changeset/ninety-cheetahs-remain.md b/.changeset/ninety-cheetahs-remain.md new file mode 100644 index 00000000..5a63a85c --- /dev/null +++ b/.changeset/ninety-cheetahs-remain.md @@ -0,0 +1,5 @@ +--- +"svelte-eslint-parser": patch +--- + +fix: crash in `@typescript-eslint/no-misused-promises` rule