|
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.extractStoreReferences = extractStoreReferences; |
|
exports.createStoreChecker = createStoreChecker; |
|
const eslint_utils_1 = require("@eslint-community/eslint-utils"); |
|
const ts_utils_1 = require("../../utils/ts-utils"); |
|
const ast_utils_1 = require("../../utils/ast-utils"); |
|
const compat_1 = require("../../utils/compat"); |
|
|
|
function* extractStoreReferences(context, storeNames = ['writable', 'readable', 'derived']) { |
|
const referenceTracker = new eslint_utils_1.ReferenceTracker((0, compat_1.getSourceCode)(context).scopeManager.globalScope); |
|
for (const { node, path } of referenceTracker.iterateEsmReferences({ |
|
'svelte/store': { |
|
[eslint_utils_1.ReferenceTracker.ESM]: true, |
|
writable: { |
|
[eslint_utils_1.ReferenceTracker.CALL]: storeNames.includes('writable') |
|
}, |
|
readable: { |
|
[eslint_utils_1.ReferenceTracker.CALL]: storeNames.includes('readable') |
|
}, |
|
derived: { |
|
[eslint_utils_1.ReferenceTracker.CALL]: storeNames.includes('derived') |
|
} |
|
} |
|
})) { |
|
yield { |
|
node: node, |
|
name: path[path.length - 1] |
|
}; |
|
} |
|
} |
|
|
|
|
|
|
|
function createStoreChecker(context) { |
|
const tools = (0, ts_utils_1.getTypeScriptTools)(context); |
|
const checker = tools ? createStoreCheckerForTS(tools) : createStoreCheckerForES(context); |
|
return (node, options) => checker(node, { |
|
consistent: options?.consistent ?? false |
|
}); |
|
} |
|
|
|
|
|
|
|
function createStoreCheckerForES(context) { |
|
const storeVariables = new Map(); |
|
for (const { node } of extractStoreReferences(context)) { |
|
const parent = (0, ast_utils_1.getParent)(node); |
|
if (!parent || parent.type !== 'VariableDeclarator' || parent.id.type !== 'Identifier') { |
|
continue; |
|
} |
|
const decl = (0, ast_utils_1.getParent)(parent); |
|
if (!decl || decl.type !== 'VariableDeclaration') { |
|
continue; |
|
} |
|
const variable = (0, ast_utils_1.findVariable)(context, parent.id); |
|
if (variable) { |
|
storeVariables.set(variable, { const: decl.kind === 'const' }); |
|
} |
|
} |
|
return (node, options) => { |
|
if (node.type !== 'Identifier' || node.name.startsWith('$')) { |
|
return false; |
|
} |
|
const variable = (0, ast_utils_1.findVariable)(context, node); |
|
if (!variable) { |
|
return false; |
|
} |
|
const info = storeVariables.get(variable); |
|
if (!info) { |
|
return false; |
|
} |
|
return options.consistent ? info.const : true; |
|
}; |
|
} |
|
|
|
|
|
|
|
function createStoreCheckerForTS(tools) { |
|
const { service } = tools; |
|
const checker = service.program.getTypeChecker(); |
|
const tsNodeMap = service.esTreeNodeToTSNodeMap; |
|
return (node, options) => { |
|
const tsNode = tsNodeMap.get(node); |
|
if (!tsNode) { |
|
return false; |
|
} |
|
const type = checker.getTypeAtLocation(tsNode); |
|
return isStoreType(checker.getApparentType(type)); |
|
|
|
|
|
|
|
function isStoreType(type) { |
|
return eachTypeCheck(type, options, (type) => { |
|
const subscribe = type.getProperty('subscribe'); |
|
if (!subscribe) { |
|
return false; |
|
} |
|
const subscribeType = checker.getTypeOfSymbolAtLocation(subscribe, tsNode); |
|
return isStoreSubscribeSignatureType(subscribeType); |
|
}); |
|
} |
|
|
|
|
|
|
|
function isStoreSubscribeSignatureType(type) { |
|
return eachTypeCheck(type, options, (type) => { |
|
for (const signature of type.getCallSignatures()) { |
|
if (signature.parameters.length >= 2 && |
|
maybeFunctionSymbol(signature.parameters[0]) && |
|
maybeFunctionSymbol(signature.parameters[1])) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
}); |
|
} |
|
|
|
|
|
|
|
function maybeFunctionSymbol(param) { |
|
const type = checker.getApparentType(checker.getTypeOfSymbolAtLocation(param, tsNode)); |
|
return maybeFunctionType(type); |
|
} |
|
|
|
|
|
|
|
function maybeFunctionType(type) { |
|
return eachTypeCheck(type, { consistent: false }, (type) => { |
|
return type.getCallSignatures().length > 0; |
|
}); |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function eachTypeCheck(type, options, check) { |
|
if (type.isUnion()) { |
|
if (options.consistent) { |
|
return type.types.every((t) => eachTypeCheck(t, options, check)); |
|
} |
|
return type.types.some((t) => eachTypeCheck(t, options, check)); |
|
} |
|
return check(type); |
|
} |
|
|