UNPKG

svelte-eslint-parser

Version:
274 lines (273 loc) 9.65 kB
import { getFallbackKeys, traverseNodes } from "../traverse.js"; import { getEspree } from "../parser/espree.js"; import { analyze } from "eslint-scope"; import { findVariable } from "../scope/index.js"; export function parseConfig(code) { const espree = getEspree(); const ast = espree.parse(code, { range: true, loc: true, ecmaVersion: espree.latestEcmaVersion, sourceType: "module", }); // Set parent nodes. traverseNodes(ast, { enterNode(node, parent) { node.parent = parent; }, leaveNode() { /* do nothing */ }, }); // Analyze scopes. const scopeManager = analyze(ast, { ignoreEval: true, nodejsScope: false, ecmaVersion: espree.latestEcmaVersion, sourceType: "module", fallback: getFallbackKeys, }); return parseAst(ast, scopeManager); } function parseAst(ast, scopeManager) { const edd = ast.body.find((node) => node.type === "ExportDefaultDeclaration"); if (!edd) return {}; const decl = edd.declaration; if (decl.type === "ClassDeclaration" || decl.type === "FunctionDeclaration") return {}; return parseSvelteConfigExpression(decl, scopeManager); } function parseSvelteConfigExpression(node, scopeManager) { const evaluated = evaluateExpression(node, scopeManager); if (evaluated?.type !== 1 /* EvaluatedType.object */) return {}; const result = {}; // Returns only known properties. const compilerOptions = evaluated.getProperty("compilerOptions"); if (compilerOptions?.type === 1 /* EvaluatedType.object */) { result.compilerOptions = {}; const runes = compilerOptions.getProperty("runes")?.getStatic(); if (runes?.value != null) { result.compilerOptions.runes = Boolean(runes.value); } } const kit = evaluated.getProperty("kit"); if (kit?.type === 1 /* EvaluatedType.object */) { result.kit = {}; const files = kit.getProperty("files")?.getStatic(); if (files?.value != null) result.kit.files = files.value; } return result; } class EvaluatedLiteral { constructor(value) { this.type = 0 /* EvaluatedType.literal */; this.value = value; } getStatic() { return this; } } /** Evaluating an object expression. */ class EvaluatedObject { constructor(node, parseExpression) { this.type = 1 /* EvaluatedType.object */; this.cached = new Map(); this.node = node; this.parseExpression = parseExpression; } /** Gets the evaluated value of the property with the given name. */ getProperty(key) { return this.withCache(key, () => { let unknown = false; for (const prop of [...this.node.properties].reverse()) { if (prop.type === "Property") { const name = this.getKey(prop); if (name === key) return this.parseExpression(prop.value); if (name == null) unknown = true; } else if (prop.type === "SpreadElement") { const evaluated = this.parseExpression(prop.argument); if (evaluated?.type === 1 /* EvaluatedType.object */) { const value = evaluated.getProperty(key); if (value) return value; } unknown = true; } } return unknown ? null : new EvaluatedLiteral(undefined); }); } getStatic() { const object = {}; for (const prop of this.node.properties) { if (prop.type === "Property") { const name = this.getKey(prop); if (name == null) return null; const evaluated = this.withCache(name, () => this.parseExpression(prop.value))?.getStatic(); if (!evaluated) return null; object[name] = evaluated.value; } else if (prop.type === "SpreadElement") { const evaluated = this.parseExpression(prop.argument)?.getStatic(); if (!evaluated) return null; Object.assign(object, evaluated.value); } } return { value: object }; } withCache(key, parse) { if (this.cached.has(key)) return this.cached.get(key) || null; const evaluated = parse(); this.cached.set(key, evaluated); return evaluated; } getKey(node) { if (!node.computed && node.key.type === "Identifier") return node.key.name; const evaluatedKey = this.parseExpression(node.key)?.getStatic(); if (evaluatedKey) return String(evaluatedKey.value); return null; } } function evaluateExpression(node, scopeManager) { const tracked = new Map(); return parseExpression(node); function parseExpression(node) { if (node.type === "Literal") { return new EvaluatedLiteral(node.value); } if (node.type === "Identifier") { return parseIdentifier(node); } if (node.type === "ObjectExpression") { return new EvaluatedObject(node, parseExpression); } return null; } function parseIdentifier(node) { const defs = getIdentifierDefinitions(node); if (defs.length !== 1) { if (defs.length === 0 && node.name === "undefined") return new EvaluatedLiteral(undefined); return null; } const def = defs[0]; if (def.type !== "Variable") return null; if (def.parent.kind !== "const" || !def.node.init) return null; const evaluated = parseExpression(def.node.init); if (!evaluated) return null; const assigns = parsePatternAssign(def.name, def.node.id); let result = evaluated; while (assigns.length) { const assign = assigns.shift(); if (assign.type === "member") { if (result.type !== 1 /* EvaluatedType.object */) return null; const next = result.getProperty(assign.name); if (!next) return null; result = next; } else if (assign.type === "assignment") { if (result.type === 0 /* EvaluatedType.literal */ && result.value === undefined) { const next = parseExpression(assign.node.right); if (!next) return null; result = next; } } } return result; } function getIdentifierDefinitions(node) { if (tracked.has(node)) return tracked.get(node); tracked.set(node, []); const defs = findVariable(scopeManager, node)?.defs; if (!defs) return []; tracked.set(node, defs); if (defs.length !== 1) { const def = defs[0]; if (def.type === "Variable" && def.parent.kind === "const" && def.node.id.type === "Identifier" && def.node.init?.type === "Identifier") { const newDef = getIdentifierDefinitions(def.node.init); tracked.set(node, newDef); return newDef; } } return defs; } } /** * Returns the assignment path. * For example, * `let {a: {target}} = {}` * -> `[{type: "member", name: 'a'}, {type: "member", name: 'target'}]`. * `let {a: {target} = foo} = {}` * -> `[{type: "member", name: 'a'}, {type: "assignment"}, {type: "member", name: 'target'}]`. */ function parsePatternAssign(node, root) { return parse(root) || []; function parse(target) { if (node === target) { return []; } if (target.type === "Identifier") { return null; } if (target.type === "AssignmentPattern") { const left = parse(target.left); if (!left) return null; return [{ type: "assignment", node: target }, ...left]; } if (target.type === "ObjectPattern") { for (const prop of target.properties) { if (prop.type === "Property") { const name = !prop.computed && prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" ? String(prop.key.value) : null; if (!name) continue; const value = parse(prop.value); if (!value) return null; return [{ type: "member", name }, ...value]; } } return null; } if (target.type === "ArrayPattern") { for (const [index, element] of target.elements.entries()) { if (!element) continue; const value = parse(element); if (!value) return null; return [{ type: "member", name: String(index) }, ...value]; } return null; } return null; } }