diff --git a/.changeset/shy-countries-carry.md b/.changeset/shy-countries-carry.md new file mode 100644 index 000000000..064f6d184 --- /dev/null +++ b/.changeset/shy-countries-carry.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': patch +--- + +fix: preventing infinite loops in multiple rules diff --git a/packages/eslint-plugin-svelte/eslint.config.mjs b/packages/eslint-plugin-svelte/eslint.config.mjs index 7205893bf..607646a03 100644 --- a/packages/eslint-plugin-svelte/eslint.config.mjs +++ b/packages/eslint-plugin-svelte/eslint.config.mjs @@ -1,5 +1,12 @@ import * as myPlugin from '@ota-meshi/eslint-plugin'; import * as tseslint from 'typescript-eslint'; +import { createJiti } from 'jiti'; +const jiti = createJiti(import.meta.url); +const internal = { + rules: { + 'prefer-find-variable-safe': await jiti.import('./internal-rules/prefer-find-variable-safe.ts') + } +}; /** * @type {import('eslint').Linter.Config[]} @@ -54,6 +61,9 @@ const config = [ }, { files: ['src/**'], + plugins: { + internal + }, rules: { '@typescript-eslint/no-restricted-imports': [ 'error', @@ -80,7 +90,8 @@ const config = [ { object: 'context', property: 'getCwd', message: 'Use `context.cwd`' }, { object: 'context', property: 'getScope', message: 'Use src/utils/compat.ts' }, { object: 'context', property: 'parserServices', message: 'Use src/utils/compat.ts' } - ] + ], + 'internal/prefer-find-variable-safe': 'error' } }, ...tseslint.config({ diff --git a/packages/eslint-plugin-svelte/internal-rules/prefer-find-variable-safe.ts b/packages/eslint-plugin-svelte/internal-rules/prefer-find-variable-safe.ts new file mode 100644 index 000000000..ae72a12ce --- /dev/null +++ b/packages/eslint-plugin-svelte/internal-rules/prefer-find-variable-safe.ts @@ -0,0 +1,171 @@ +import type { Rule } from 'eslint'; +import { ReferenceTracker, findVariable } from '@eslint-community/eslint-utils'; +import path from 'node:path'; +import type { TSESTree } from '@typescript-eslint/types'; +import type { Variable } from '@typescript-eslint/scope-manager'; + +export default { + meta: { + docs: { + description: 'enforce to use FindVariableContext to avoid infinite recursion', + category: 'Best Practices', + recommended: false, + conflictWithPrettier: false, + url: 'https://github.com/sveltejs/eslint-plugin-svelte/blob/v3.12.3/docs/rules/prefer-find-variable-safe.md' + }, + messages: { + preferFindVariableSafe: 'Prefer to use FindVariableContext to avoid infinite recursion.' + }, + schema: [], + type: 'suggestion' + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const referenceTracker = new ReferenceTracker( + context.sourceCode.scopeManager.globalScope as never + ); + let astUtilsPath = path.relative( + path.dirname(context.physicalFilename), + path.join(import.meta.dirname, '..', 'src', 'utils', 'ast-utils') + ); + if (!astUtilsPath.startsWith('.')) { + astUtilsPath = `./${astUtilsPath}`; + } + const findVariableCalls = [ + ...referenceTracker.iterateEsmReferences({ + [astUtilsPath]: { + [ReferenceTracker.ESM]: true, + findVariable: { + [ReferenceTracker.CALL]: true + } + }, + [`${astUtilsPath}.js`]: { + [ReferenceTracker.ESM]: true, + findVariable: { + [ReferenceTracker.CALL]: true + } + }, + [`${astUtilsPath}.ts`]: { + [ReferenceTracker.ESM]: true, + findVariable: { + [ReferenceTracker.CALL]: true + } + } + }) + ]; + type FunctionContext = { + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression; + identifier: TSESTree.Identifier | null; + findVariableCall?: TSESTree.CallExpression; + calls: Set; + upper: FunctionContext | null; + }; + let functionStack: FunctionContext | null = null; + const functionContexts: FunctionContext[] = []; + + function getFunctionVariableName( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression + ) { + if (node.type === 'FunctionDeclaration') { + return node.id; + } + if (node.parent?.type === 'VariableDeclarator' && node.parent.id.type === 'Identifier') { + return node.parent.id; + } + return null; + } + + function* iterateVariables(node: TSESTree.Identifier) { + const visitedNodes = new Set(); + let currentNode: TSESTree.Identifier | null = node; + while (currentNode) { + if (visitedNodes.has(currentNode)) break; + const variable = findVariable( + context.sourceCode.getScope(currentNode), + currentNode + ) as Variable | null; + if (!variable) break; + yield variable; + const def = variable.defs[0]; + if (!def) break; + if (def.type !== 'Variable' || !def.node.init) break; + if (def.node.init.type !== 'Identifier') break; + currentNode = def.node.init; + visitedNodes.add(currentNode); + } + } + + /** + * Verify a function context to report if necessary. + * Reports when a function contains a call to findVariable and the function is recursive. + */ + function verifyFunctionContext(functionContext: FunctionContext) { + if (!functionContext.findVariableCall) return; + if (!hasRecursive(functionContext)) return; + context.report({ + node: functionContext.findVariableCall, + messageId: 'preferFindVariableSafe' + }); + } + + function hasRecursive(functionContext: FunctionContext) { + const buffer = [functionContext]; + const visitedContext = new Set(); + let current; + while ((current = buffer.shift())) { + if (visitedContext.has(current)) continue; + visitedContext.add(current); + if (!current.identifier) continue; + for (const variable of iterateVariables(current.identifier)) { + for (const { identifier } of variable.references) { + if (identifier.type !== 'Identifier') continue; + if (functionContext.calls.has(identifier)) { + return true; + } + buffer.push(...functionContexts.filter((ctx) => ctx.calls.has(identifier))); + } + } + } + return false; + } + + return { + ':function'( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression + ) { + functionStack = { + node, + identifier: getFunctionVariableName(node), + calls: new Set(), + upper: functionStack + }; + functionContexts.push(functionStack); + }, + ':function:exit'() { + functionStack = functionStack?.upper || null; + }, + CallExpression(node) { + if (!functionStack) return; + if (findVariableCalls.some((call) => call.node === node)) { + functionStack.findVariableCall = node; + } + if (node.callee.type === 'Identifier') { + functionStack.calls.add(node.callee); + } + }, + 'Program:exit'() { + for (const functionContext of functionContexts) { + verifyFunctionContext(functionContext); + } + } + }; + } +}; diff --git a/packages/eslint-plugin-svelte/package.json b/packages/eslint-plugin-svelte/package.json index ac16e9a09..88e44d3da 100644 --- a/packages/eslint-plugin-svelte/package.json +++ b/packages/eslint-plugin-svelte/package.json @@ -91,6 +91,7 @@ "eslint-typegen": "^2.3.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", + "jiti": "^2.5.1", "less": "^4.4.1", "mocha": "~11.7.2", "postcss-nested": "^7.0.2", diff --git a/packages/eslint-plugin-svelte/src/rules/no-dynamic-slot-name.ts b/packages/eslint-plugin-svelte/src/rules/no-dynamic-slot-name.ts index 928766713..2c421761e 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-dynamic-slot-name.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-dynamic-slot-name.ts @@ -2,7 +2,7 @@ import type { AST } from 'svelte-eslint-parser'; import type { TSESTree } from '@typescript-eslint/types'; import { createRule } from '../utils/index.js'; import { - findVariable, + FindVariableContext, getAttributeValueQuoteAndRange, getStringIfConstant } from '../utils/ast-utils.js'; @@ -67,20 +67,19 @@ export default createRule('no-dynamic-slot-name', { * Get static text from given expression */ function getStaticText(node: TSESTree.Expression) { - const expr = findRootExpression(node); + const expr = findRootExpression(new FindVariableContext(context), node); return getStringIfConstant(expr); } /** Find data expression */ function findRootExpression( - node: TSESTree.Expression, - already = new Set() + ctx: FindVariableContext, + node: TSESTree.Expression ): TSESTree.Expression { - if (node.type !== 'Identifier' || already.has(node)) { + if (node.type !== 'Identifier') { return node; } - already.add(node); - const variable = findVariable(context, node); + const variable = ctx.findVariable(node); if (!variable || variable.defs.length !== 1) { return node; } @@ -88,7 +87,7 @@ export default createRule('no-dynamic-slot-name', { if (def.type === 'Variable') { if (def.parent.kind === 'const' && def.node.init) { const init = def.node.init; - return findRootExpression(init, already); + return findRootExpression(ctx, init); } } return node; diff --git a/packages/eslint-plugin-svelte/src/rules/no-immutable-reactive-statements.ts b/packages/eslint-plugin-svelte/src/rules/no-immutable-reactive-statements.ts index 21e56bb86..034fe6082 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-immutable-reactive-statements.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-immutable-reactive-statements.ts @@ -2,7 +2,7 @@ import type { AST } from 'svelte-eslint-parser'; import { createRule } from '../utils/index.js'; import type { Scope, Variable, Reference, Definition } from '@typescript-eslint/scope-manager'; import type { TSESTree } from '@typescript-eslint/types'; -import { findVariable, iterateIdentifiers } from '../utils/ast-utils.js'; +import { FindVariableContext, iterateIdentifiers } from '../utils/ast-utils.js'; export default createRule('no-immutable-reactive-statements', { meta: { @@ -91,7 +91,7 @@ export default createRule('no-immutable-reactive-statements', { return true; } } - return hasWrite(variable); + return hasWrite(new FindVariableContext(context), variable); } return false; }); @@ -100,7 +100,7 @@ export default createRule('no-immutable-reactive-statements', { } /** Checks whether the given variable has a write or reactive store reference or not. */ - function hasWrite(variable: Variable) { + function hasWrite(ctx: FindVariableContext, variable: Variable) { const defIds = variable.defs.map((def: Definition) => def.name); for (const reference of variable.references) { if ( @@ -113,7 +113,7 @@ export default createRule('no-immutable-reactive-statements', { ) { return true; } - if (hasWriteMember(reference.identifier)) { + if (hasWriteMember(ctx, reference.identifier)) { return true; } } @@ -122,6 +122,7 @@ export default createRule('no-immutable-reactive-statements', { /** Checks whether the given expression has writing to a member or not. */ function hasWriteMember( + ctx: FindVariableContext, expr: TSESTree.Identifier | TSESTree.JSXIdentifier | TSESTree.MemberExpression ): boolean { if (expr.type === 'JSXIdentifier') return false; @@ -136,14 +137,16 @@ export default createRule('no-immutable-reactive-statements', { return parent.operator === 'delete' && parent.argument === expr; } if (parent.type === 'MemberExpression') { - return parent.object === expr && hasWriteMember(parent); + return parent.object === expr && hasWriteMember(ctx, parent); } if (parent.type === 'SvelteDirective') { return parent.kind === 'Binding' && parent.expression === expr; } if (parent.type === 'SvelteEachBlock') { return ( - parent.context !== null && parent.expression === expr && hasWriteReference(parent.context) + parent.context !== null && + parent.expression === expr && + hasWriteReference(ctx, parent.context) ); } @@ -151,10 +154,13 @@ export default createRule('no-immutable-reactive-statements', { } /** Checks whether the given pattern has writing or not. */ - function hasWriteReference(pattern: TSESTree.DestructuringPattern): boolean { + function hasWriteReference( + ctx: FindVariableContext, + pattern: TSESTree.DestructuringPattern + ): boolean { for (const id of iterateIdentifiers(pattern)) { - const variable = findVariable(context, id); - if (variable && hasWrite(variable)) return true; + const variable = ctx.findVariable(id); + if (variable && hasWrite(ctx, variable)) return true; } return false; diff --git a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts index 98c34b9c3..110576e22 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-resolve.ts @@ -1,6 +1,7 @@ import type { TSESTree } from '@typescript-eslint/types'; import { createRule } from '../utils/index.js'; import { ReferenceTracker } from '@eslint-community/eslint-utils'; +import { FindVariableContext } from '../utils/ast-utils.js'; import { findVariable } from '../utils/ast-utils.js'; import type { RuleContext } from '../types.js'; import type { AST } from 'svelte-eslint-parser'; @@ -101,7 +102,11 @@ export default createRule('no-navigation-without-resolve', { (node.value[0].type === 'SvelteMustacheTag' && !expressionIsAbsolute(node.value[0].expression) && !expressionIsFragment(node.value[0].expression) && - !isResolveCall(context, node.value[0].expression, resolveReferences)) + !isResolveCall( + new FindVariableContext(context), + node.value[0].expression, + resolveReferences + )) ) { context.report({ loc: node.value[0].loc, messageId: 'linkWithoutResolve' }); } @@ -191,7 +196,7 @@ function checkGotoCall( return; } const url = call.arguments[0]; - if (!isResolveCall(context, url, resolveReferences)) { + if (!isResolveCall(new FindVariableContext(context), url, resolveReferences)) { context.report({ loc: url.loc, messageId: 'gotoWithoutResolve' }); } } @@ -206,7 +211,10 @@ function checkShallowNavigationCall( return; } const url = call.arguments[0]; - if (!expressionIsEmpty(url) && !isResolveCall(context, url, resolveReferences)) { + if ( + !expressionIsEmpty(url) && + !isResolveCall(new FindVariableContext(context), url, resolveReferences) + ) { context.report({ loc: url.loc, messageId }); } } @@ -214,7 +222,7 @@ function checkShallowNavigationCall( // Helper functions function isResolveCall( - context: RuleContext, + ctx: FindVariableContext, node: TSESTree.CallExpressionArgument, resolveReferences: Set ): boolean { @@ -228,13 +236,13 @@ function isResolveCall( return true; } if (node.type === 'Identifier') { - const variable = findVariable(context, node); + const variable = ctx.findVariable(node); if ( variable !== null && variable.identifiers.length > 0 && variable.identifiers[0].parent.type === 'VariableDeclarator' && variable.identifiers[0].parent.init !== null && - isResolveCall(context, variable.identifiers[0].parent.init, resolveReferences) + isResolveCall(ctx, variable.identifiers[0].parent.init, resolveReferences) ) { return true; } diff --git a/packages/eslint-plugin-svelte/src/rules/no-not-function-handler.ts b/packages/eslint-plugin-svelte/src/rules/no-not-function-handler.ts index 094090c56..f98ea2068 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-not-function-handler.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-not-function-handler.ts @@ -1,8 +1,8 @@ import type { AST } from 'svelte-eslint-parser'; import type { TSESTree } from '@typescript-eslint/types'; import { createRule } from '../utils/index.js'; -import { findVariable } from '../utils/ast-utils.js'; import { EVENT_NAMES } from '../utils/events.js'; +import { FindVariableContext } from '../utils/ast-utils.js'; const PHRASES = { ObjectExpression: 'object', @@ -38,14 +38,13 @@ export default createRule('no-not-function-handler', { create(context) { /** Find data expression */ function findRootExpression( - node: TSESTree.Expression, - already = new Set() + ctx: FindVariableContext, + node: TSESTree.Expression ): TSESTree.Expression { - if (node.type !== 'Identifier' || already.has(node)) { + if (node.type !== 'Identifier') { return node; } - already.add(node); - const variable = findVariable(context, node); + const variable = ctx.findVariable(node); if (!variable || variable.defs.length !== 1) { return node; } @@ -53,7 +52,7 @@ export default createRule('no-not-function-handler', { if (def.type === 'Variable') { if (def.parent.kind === 'const' && def.node.init) { const init = def.node.init; - return findRootExpression(init, already); + return findRootExpression(ctx, init); } } return node; @@ -64,7 +63,7 @@ export default createRule('no-not-function-handler', { if (!node) { return; } - const expression = findRootExpression(node); + const expression = findRootExpression(new FindVariableContext(context), node); if ( expression.type !== 'ObjectExpression' && diff --git a/packages/eslint-plugin-svelte/src/utils/ast-utils.ts b/packages/eslint-plugin-svelte/src/utils/ast-utils.ts index e36a9cf75..2ce0d0c10 100644 --- a/packages/eslint-plugin-svelte/src/utils/ast-utils.ts +++ b/packages/eslint-plugin-svelte/src/utils/ast-utils.ts @@ -230,6 +230,29 @@ export function findVariable(context: RuleContext, node: TSESTree.Identifier): V // Remove the $ and search for the variable again, as it may be a store access variable. return eslintUtils.findVariable(initialScope, node.name.slice(1)); } + +/** + * Context for safely finding variables, avoiding infinite recursion. + * This should be used when the caller function may be called recursively, instead of `findVariable()`. + * + * Create an instance of this class at the top of the call stack where you want to start searching for identifiers, + * and then use it within the recursion. + */ +export class FindVariableContext { + public readonly findVariable: (node: TSESTree.Identifier) => Variable | null; + + public constructor(context: RuleContext) { + const visited = new Set(); + this.findVariable = (node: TSESTree.Identifier) => { + if (visited.has(node)) { + return null; + } + visited.add(node); + return findVariable(context, node); + }; + } +} + /** * Iterate the identifiers of a given pattern node. */ diff --git a/packages/eslint-plugin-svelte/src/utils/expression-affixes.ts b/packages/eslint-plugin-svelte/src/utils/expression-affixes.ts index 53b6562bc..74d94663a 100644 --- a/packages/eslint-plugin-svelte/src/utils/expression-affixes.ts +++ b/packages/eslint-plugin-svelte/src/utils/expression-affixes.ts @@ -1,13 +1,34 @@ import type { TSESTree } from '@typescript-eslint/types'; -import { findVariable } from './ast-utils.js'; import type { RuleContext } from '../types.js'; import type { AST } from 'svelte-eslint-parser'; - -// Variable prefix extraction +import { FindVariableContext } from './ast-utils.js'; export function extractExpressionPrefixVariable( context: RuleContext, expression: TSESTree.Expression +): TSESTree.Identifier | null { + return extractExpressionPrefixVariableInternal(new FindVariableContext(context), expression); +} + +export function extractExpressionPrefixLiteral( + context: RuleContext, + expression: AST.SvelteLiteral | TSESTree.Node +): string | null { + return extractExpressionPrefixLiteralInternal(new FindVariableContext(context), expression); +} + +export function extractExpressionSuffixLiteral( + context: RuleContext, + expression: AST.SvelteLiteral | TSESTree.Node +): string | null { + return extractExpressionSuffixLiteralInternal(new FindVariableContext(context), expression); +} + +// Variable prefix extraction + +function extractExpressionPrefixVariableInternal( + context: FindVariableContext, + expression: TSESTree.Expression ): TSESTree.Identifier | null { switch (expression.type) { case 'BinaryExpression': @@ -24,19 +45,19 @@ export function extractExpressionPrefixVariable( } function extractBinaryExpressionPrefixVariable( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.BinaryExpression ): TSESTree.Identifier | null { return expression.left.type !== 'PrivateIdentifier' - ? extractExpressionPrefixVariable(context, expression.left) + ? extractExpressionPrefixVariableInternal(context, expression.left) : null; } function extractVariablePrefixVariable( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.Identifier ): TSESTree.Identifier | null { - const variable = findVariable(context, expression); + const variable = context.findVariable(expression); if ( variable === null || variable.identifiers.length !== 1 || @@ -46,7 +67,8 @@ function extractVariablePrefixVariable( return expression; } return ( - extractExpressionPrefixVariable(context, variable.identifiers[0].parent.init) ?? expression + extractExpressionPrefixVariableInternal(context, variable.identifiers[0].parent.init) ?? + expression ); } @@ -57,7 +79,7 @@ function extractMemberExpressionPrefixVariable( } function extractTemplateLiteralPrefixVariable( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.TemplateLiteral ): TSESTree.Identifier | null { const literalParts = [...expression.expressions, ...expression.quasis].sort((a, b) => @@ -69,7 +91,7 @@ function extractTemplateLiteralPrefixVariable( continue; } if (part.type !== 'TemplateElement') { - return extractExpressionPrefixVariable(context, part); + return extractExpressionPrefixVariableInternal(context, part); } return null; } @@ -78,8 +100,8 @@ function extractTemplateLiteralPrefixVariable( // Literal prefix extraction -export function extractExpressionPrefixLiteral( - context: RuleContext, +function extractExpressionPrefixLiteralInternal( + context: FindVariableContext, expression: AST.SvelteLiteral | TSESTree.Node ): string | null { switch (expression.type) { @@ -99,19 +121,19 @@ export function extractExpressionPrefixLiteral( } function extractBinaryExpressionPrefixLiteral( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.BinaryExpression ): string | null { return expression.left.type !== 'PrivateIdentifier' - ? extractExpressionPrefixLiteral(context, expression.left) + ? extractExpressionPrefixLiteralInternal(context, expression.left) : null; } function extractVariablePrefixLiteral( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.Identifier ): string | null { - const variable = findVariable(context, expression); + const variable = context.findVariable(expression); if ( variable === null || variable.identifiers.length !== 1 || @@ -120,11 +142,11 @@ function extractVariablePrefixLiteral( ) { return null; } - return extractExpressionPrefixLiteral(context, variable.identifiers[0].parent.init); + return extractExpressionPrefixLiteralInternal(context, variable.identifiers[0].parent.init); } function extractTemplateLiteralPrefixLiteral( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.TemplateLiteral ): string | null { const literalParts = [...expression.expressions, ...expression.quasis].sort((a, b) => @@ -138,15 +160,15 @@ function extractTemplateLiteralPrefixLiteral( } return part.value.raw; } - return extractExpressionPrefixLiteral(context, part); + return extractExpressionPrefixLiteralInternal(context, part); } return null; } // Literal suffix extraction -export function extractExpressionSuffixLiteral( - context: RuleContext, +function extractExpressionSuffixLiteralInternal( + context: FindVariableContext, expression: AST.SvelteLiteral | TSESTree.Node ): string | null { switch (expression.type) { @@ -166,17 +188,17 @@ export function extractExpressionSuffixLiteral( } function extractBinaryExpressionSuffixLiteral( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.BinaryExpression ): string | null { - return extractExpressionSuffixLiteral(context, expression.right); + return extractExpressionSuffixLiteralInternal(context, expression.right); } function extractVariableSuffixLiteral( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.Identifier ): string | null { - const variable = findVariable(context, expression); + const variable = context.findVariable(expression); if ( variable === null || variable.identifiers.length !== 1 || @@ -185,11 +207,11 @@ function extractVariableSuffixLiteral( ) { return null; } - return extractExpressionSuffixLiteral(context, variable.identifiers[0].parent.init); + return extractExpressionSuffixLiteralInternal(context, variable.identifiers[0].parent.init); } function extractTemplateLiteralSuffixLiteral( - context: RuleContext, + context: FindVariableContext, expression: TSESTree.TemplateLiteral ): string | null { const literalParts = [...expression.expressions, ...expression.quasis].sort((a, b) => @@ -203,7 +225,7 @@ function extractTemplateLiteralSuffixLiteral( } return part.value.raw; } - return extractExpressionSuffixLiteral(context, part); + return extractExpressionSuffixLiteralInternal(context, part); } return null; } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/consistent-selector-style/valid/type-id-class/recursive-loop01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/consistent-selector-style/valid/type-id-class/recursive-loop01-input.svelte new file mode 100644 index 000000000..2475b85e5 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/consistent-selector-style/valid/type-id-class/recursive-loop01-input.svelte @@ -0,0 +1,6 @@ + + +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-errors.yaml new file mode 100644 index 000000000..b44c7a3c6 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-errors.yaml @@ -0,0 +1,12 @@ +- message: '`` name cannot be dynamic.' + line: 6 + column: 12 + suggestions: null +- message: '`` name cannot be dynamic.' + line: 7 + column: 12 + suggestions: null +- message: '`` name cannot be dynamic.' + line: 8 + column: 12 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-input.svelte new file mode 100644 index 000000000..dc09fc29c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-input.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-output.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-output.svelte new file mode 100644 index 000000000..fe11a0aa1 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/multi-report01-output.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-errors.yaml new file mode 100644 index 000000000..a625cd83b --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-errors.yaml @@ -0,0 +1,4 @@ +- message: '`` name cannot be dynamic.' + line: 6 + column: 12 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-input.svelte new file mode 100644 index 000000000..104d1e910 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-input.svelte @@ -0,0 +1,6 @@ + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-output.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-output.svelte new file mode 100644 index 000000000..104d1e910 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-dynamic-slot-name/invalid/recursive-loop01-output.svelte @@ -0,0 +1,6 @@ + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/recursive-loop01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/recursive-loop01-errors.yaml new file mode 100644 index 000000000..6d3676e1a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/recursive-loop01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a link with a url that isn't resolved. + line: 6 + column: 9 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/recursive-loop01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/recursive-loop01-input.svelte new file mode 100644 index 000000000..0e196356f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-resolve/invalid/recursive-loop01-input.svelte @@ -0,0 +1,6 @@ + + +Click me! diff --git a/packages/eslint-plugin-svelte/tsconfig.build.json b/packages/eslint-plugin-svelte/tsconfig.build.json index bb09a4ee7..92dc4dc4f 100644 --- a/packages/eslint-plugin-svelte/tsconfig.build.json +++ b/packages/eslint-plugin-svelte/tsconfig.build.json @@ -5,6 +5,8 @@ "tools/**/*", "typings/**/*", "vite.config.mts", - "docs-svelte-kit/**/*.mts" + "docs-svelte-kit/**/*.mts", + "internal-rules/**/*", + "eslint.config.mts" ] } diff --git a/packages/eslint-plugin-svelte/tsconfig.json b/packages/eslint-plugin-svelte/tsconfig.json index 800422509..423165a8f 100644 --- a/packages/eslint-plugin-svelte/tsconfig.json +++ b/packages/eslint-plugin-svelte/tsconfig.json @@ -26,7 +26,9 @@ "tests/utils/**/*", "tools/**/*", "vite.config.mts", - "docs-svelte-kit/**/*.mts" + "docs-svelte-kit/**/*.mts", + "internal-rules/**/*", + "eslint.config.mts" ], "exclude": ["lib/**/*", "tests/fixtures/**/*"] }