UNPKG

eslint-plugin-svelte

Version:
271 lines (270 loc) 9.26 kB
import { ReferenceTracker } from '@eslint-community/eslint-utils'; import { createRule } from '../utils/index.js'; import { findVariable, isIn } from '../utils/ast-utils.js'; import { getSvelteContext } from '../utils/svelte-context.js'; export default createRule('prefer-svelte-reactivity', { meta: { docs: { description: 'disallow using mutable instances of built-in classes where a reactive alternative is provided by svelte/reactivity', category: 'Possible Errors', recommended: true }, schema: [], messages: { mutableDateUsed: 'Found a mutable instance of the built-in Date class. Use SvelteDate instead.', mutableMapUsed: 'Found a mutable instance of the built-in Map class. Use SvelteMap instead.', mutableSetUsed: 'Found a mutable instance of the built-in Set class. Use SvelteSet instead.', mutableURLUsed: 'Found a mutable instance of the built-in URL class. Use SvelteURL instead.', mutableURLSearchParamsUsed: 'Found a mutable instance of the built-in URLSearchParams class. Use SvelteURLSearchParams instead.' }, type: 'problem', conditions: [ { svelteVersions: ['5'], svelteFileTypes: ['.svelte', '.svelte.[js|ts]'] } ] }, create(context) { const exportedVars = []; return { ...(getSvelteContext(context)?.svelteFileType === '.svelte.[js|ts]' && { ExportNamedDeclaration(node) { if (node.declaration !== null) { exportedVars.push(node.declaration); } for (const specifier of node.specifiers) { if (specifier.local.type !== 'Identifier') { continue; } const defs = findVariable(context, specifier.local)?.defs ?? []; for (const def of defs) { exportedVars.push(def.node); } } }, ExportDefaultDeclaration(node) { if (node.declaration.type === 'Identifier') { const defs = findVariable(context, node.declaration)?.defs ?? []; for (const def of defs) { exportedVars.push(def.node); } } else { exportedVars.push(node.declaration); } } }), 'Program:exit'() { const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope); for (const { node, path } of referenceTracker.iterateGlobalReferences({ Date: { [ReferenceTracker.CONSTRUCT]: true }, Map: { [ReferenceTracker.CONSTRUCT]: true }, Set: { [ReferenceTracker.CONSTRUCT]: true }, URL: { [ReferenceTracker.CONSTRUCT]: true }, URLSearchParams: { [ReferenceTracker.CONSTRUCT]: true } })) { const messageId = path[0] === 'Date' ? 'mutableDateUsed' : path[0] === 'Map' ? 'mutableMapUsed' : path[0] === 'Set' ? 'mutableSetUsed' : path[0] === 'URL' ? 'mutableURLUsed' : 'mutableURLSearchParamsUsed'; for (const exportedVar of exportedVars) { if (isIn(node, exportedVar)) { context.report({ messageId, node }); } } if (path[0] === 'Date' && isDateMutable(referenceTracker, node)) { context.report({ messageId: 'mutableDateUsed', node }); } if (path[0] === 'Map' && isMapMutable(referenceTracker, node)) { context.report({ messageId: 'mutableMapUsed', node }); } if (path[0] === 'Set' && isSetMutable(referenceTracker, node)) { context.report({ messageId: 'mutableSetUsed', node }); } if (path[0] === 'URL' && isURLMutable(referenceTracker, node)) { context.report({ messageId: 'mutableURLUsed', node }); } if (path[0] === 'URLSearchParams' && isURLSearchParamsMutable(referenceTracker, node)) { context.report({ messageId: 'mutableURLSearchParamsUsed', node }); } } } }; } }); function isDateMutable(referenceTracker, ctorNode) { return !referenceTracker .iteratePropertyReferences(ctorNode, { setDate: { [ReferenceTracker.CALL]: true }, setFullYear: { [ReferenceTracker.CALL]: true }, setHours: { [ReferenceTracker.CALL]: true }, setMilliseconds: { [ReferenceTracker.CALL]: true }, setMinutes: { [ReferenceTracker.CALL]: true }, setMonth: { [ReferenceTracker.CALL]: true }, setSeconds: { [ReferenceTracker.CALL]: true }, setTime: { [ReferenceTracker.CALL]: true }, setUTCDate: { [ReferenceTracker.CALL]: true }, setUTCFullYear: { [ReferenceTracker.CALL]: true }, setUTCHours: { [ReferenceTracker.CALL]: true }, setUTCMilliseconds: { [ReferenceTracker.CALL]: true }, setUTCMinutes: { [ReferenceTracker.CALL]: true }, setUTCMonth: { [ReferenceTracker.CALL]: true }, setUTCSeconds: { [ReferenceTracker.CALL]: true }, setYear: { [ReferenceTracker.CALL]: true } }) .next().done; } function isMapMutable(referenceTracker, ctorNode) { return !referenceTracker .iteratePropertyReferences(ctorNode, { clear: { [ReferenceTracker.CALL]: true }, delete: { [ReferenceTracker.CALL]: true }, set: { [ReferenceTracker.CALL]: true } }) .next().done; } function isSetMutable(referenceTracker, ctorNode) { return !referenceTracker .iteratePropertyReferences(ctorNode, { add: { [ReferenceTracker.CALL]: true }, clear: { [ReferenceTracker.CALL]: true }, delete: { [ReferenceTracker.CALL]: true } }) .next().done; } function isURLMutable(referenceTracker, ctorNode) { for (const { node } of referenceTracker.iteratePropertyReferences(ctorNode, { hash: { [ReferenceTracker.READ]: true }, host: { [ReferenceTracker.READ]: true }, hostname: { [ReferenceTracker.READ]: true }, href: { [ReferenceTracker.READ]: true }, password: { [ReferenceTracker.READ]: true }, pathname: { [ReferenceTracker.READ]: true }, port: { [ReferenceTracker.READ]: true }, protocol: { [ReferenceTracker.READ]: true }, search: { [ReferenceTracker.READ]: true }, username: { [ReferenceTracker.READ]: true } })) { if (node.parent.type === 'AssignmentExpression' && node.parent.left === node) { return true; } } return false; } function isURLSearchParamsMutable(referenceTracker, ctorNode) { return !referenceTracker .iteratePropertyReferences(ctorNode, { append: { [ReferenceTracker.CALL]: true }, delete: { [ReferenceTracker.CALL]: true }, set: { [ReferenceTracker.CALL]: true }, sort: { [ReferenceTracker.CALL]: true } }) .next().done; }