UNPKG

svelte-eslint-parser

Version:
520 lines (519 loc) 19 kB
import { convertChildren } from "./element.js"; import { getWithLoc, indexOf, lastIndexOf } from "./common.js"; import { getAlternateFromIfBlock, getBodyFromEachBlock, getCatchFromAwaitBlock, getChildren, getConsequentFromIfBlock, getFallbackFromEachBlock, getFragment, getPendingFromAwaitBlock, getTestFromIfBlock, getThenFromAwaitBlock, trimChildren, } from "../compat.js"; /** Get start index of block */ function startBlockIndex(code, endIndex, block) { return lastIndexOf(code, (c, index) => { if (c !== "{") { return false; } for (let next = index + 1; next < code.length; next++) { const nextC = code[next]; if (!nextC.trim()) { continue; } return code.startsWith(block, next); } return false; }, endIndex); } function startIndexFromFragment(fragment, getBeforeEndIndex) { if (fragment.start != null) { return fragment.start; } const children = getChildren(fragment); return children.length ? children[0].start : getBeforeEndIndex(); } function endIndexFromFragment(fragment, getBeforeEndIndex) { if (fragment.end != null) { return fragment.end; } const children = getChildren(fragment); return children.length ? children[children.length - 1].end : getBeforeEndIndex(); } function endIndexFromBlock(fragment, lastExpression, ctx) { return endIndexFromFragment(fragment, () => { return ctx.code.indexOf("}", getWithLoc(lastExpression).end) + 1; }); } /** Convert for IfBlock */ export function convertIfBlock(node, parent, ctx, elseifContext) { // {#if expr} {:else} {/if} // {:else if expr} {/if} const elseif = Boolean(elseifContext); const nodeStart = startBlockIndex(ctx.code, elseifContext?.start ?? node.start, elseif ? ":else" : "#if"); const ifBlock = { type: "SvelteIfBlock", elseif: Boolean(elseif), expression: null, children: [], else: null, parent, ...ctx.getConvertLocation({ start: nodeStart, end: node.end }), }; const test = getTestFromIfBlock(node); ctx.scriptLet.nestIfBlock(test, ifBlock, (es) => { ifBlock.expression = es; }); const consequent = getConsequentFromIfBlock(node); for (const child of convertChildren({ nodes: // Adjust for Svelte v5 trimChildren(getChildren(consequent)), }, ifBlock, ctx)) { ifBlock.children.push(child); } ctx.scriptLet.closeScope(); if (elseif) { const index = ctx.code.indexOf("if", nodeStart); ctx.addToken("MustacheKeyword", { start: index, end: index + 2 }); } extractMustacheBlockTokens(ifBlock, ctx, { startOnly: elseif }); const elseFragment = getAlternateFromIfBlock(node); if (!elseFragment) { return ifBlock; } const elseStart = startBlockIndexForElse(elseFragment, consequent, test, ctx); const elseChildren = getChildren(elseFragment); if (elseChildren.length === 1) { const c = elseChildren[0]; if (c.type === "IfBlock" && c.elseif) { const elseBlock = { type: "SvelteElseBlock", elseif: true, children: [], parent: ifBlock, ...ctx.getConvertLocation({ start: elseStart, end: c.end, }), }; ifBlock.else = elseBlock; const elseIfBlock = convertIfBlock(c, elseBlock, ctx, { start: elseStart, }); // adjust loc elseBlock.range[1] = elseIfBlock.range[1]; elseBlock.loc.end = { line: elseIfBlock.loc.end.line, column: elseIfBlock.loc.end.column, }; elseBlock.children = [elseIfBlock]; return ifBlock; } } const elseBlock = { type: "SvelteElseBlock", elseif: false, children: [], parent: ifBlock, ...ctx.getConvertLocation({ start: elseStart, end: endIndexFromFragment(elseFragment, () => ctx.code.indexOf("}", elseStart + 5) + 1), }), }; ifBlock.else = elseBlock; ctx.scriptLet.nestBlock(elseBlock); for (const child of convertChildren({ nodes: // Adjust for Svelte v5 trimChildren(elseChildren), }, elseBlock, ctx)) { elseBlock.children.push(child); } ctx.scriptLet.closeScope(); extractMustacheBlockTokens(elseBlock, ctx, { startOnly: true }); return ifBlock; } function startBlockIndexForElse(elseFragment, beforeFragment, lastExpression, ctx) { const elseChildren = getChildren(elseFragment); if (elseChildren.length > 0) { const c = elseChildren[0]; if (c.type === "IfBlock" && c.elseif) { const contentStart = getWithLoc(getTestFromIfBlock(c)).start; if (contentStart <= c.start) { return startBlockIndex(ctx.code, contentStart - 1, ":else"); } } return startBlockIndex(ctx.code, c.start, ":else"); } const beforeEnd = endIndexFromFragment(beforeFragment, () => { return ctx.code.indexOf("}", getWithLoc(lastExpression).end) + 1; }); return startBlockIndex(ctx.code, beforeEnd, ":else"); } /** Convert for EachBlock */ export function convertEachBlock(node, parent, ctx) { // {#each expr as item, index (key)} {/each} const nodeStart = startBlockIndex(ctx.code, node.start, "#each"); const eachBlock = { type: "SvelteEachBlock", expression: null, context: null, index: null, key: null, children: [], else: null, parent, ...ctx.getConvertLocation({ start: nodeStart, end: node.end }), }; let indexRange = null; if (node.index) { const start = ctx.code.indexOf(node.index, getWithLoc(node.context ?? node.expression).end); indexRange = { start, end: start + node.index.length, }; } ctx.scriptLet.nestEachBlock(node.expression, node.context, indexRange, eachBlock, (expression, context, index) => { eachBlock.expression = expression; eachBlock.context = context; eachBlock.index = index; }); if (node.context) { const asStart = ctx.code.indexOf("as", getWithLoc(node.expression).end); ctx.addToken("Keyword", { start: asStart, end: asStart + 2, }); } if (node.key) { ctx.scriptLet.addExpression(node.key, eachBlock, null, (key) => { eachBlock.key = key; }); } const body = getBodyFromEachBlock(node); eachBlock.children.push(...convertChildren({ nodes: // Adjust for Svelte v5 trimChildren(getChildren(body)), }, eachBlock, ctx)); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(eachBlock, ctx); const fallbackFragment = getFallbackFromEachBlock(node); if (!fallbackFragment) { return eachBlock; } const elseStart = startBlockIndexForElse(fallbackFragment, body, node.key || indexRange || node.context || node.expression, ctx); const elseBlock = { type: "SvelteElseBlock", elseif: false, children: [], parent: eachBlock, ...ctx.getConvertLocation({ start: elseStart, end: endIndexFromFragment(fallbackFragment, () => elseStart), }), }; eachBlock.else = elseBlock; ctx.scriptLet.nestBlock(elseBlock); elseBlock.children.push(...convertChildren({ nodes: // Adjust for Svelte v5 trimChildren(getChildren(fallbackFragment)), }, elseBlock, ctx)); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(elseBlock, ctx, { startOnly: true }); return eachBlock; } /** Convert for AwaitBlock */ export function convertAwaitBlock(node, parent, ctx) { const nodeStart = startBlockIndex(ctx.code, node.start, "#await"); const awaitBlock = { type: "SvelteAwaitBlock", expression: null, kind: "await", pending: null, then: null, catch: null, parent, ...ctx.getConvertLocation({ start: nodeStart, end: node.end }), }; ctx.scriptLet.addExpression(node.expression, awaitBlock, null, (expression) => { awaitBlock.expression = expression; }); const pending = getPendingFromAwaitBlock(node); if (pending) { const pendingBlock = { type: "SvelteAwaitPendingBlock", children: [], parent: awaitBlock, ...ctx.getConvertLocation({ start: awaitBlock.range[0], end: endIndexFromBlock(pending, node.expression, ctx), }), }; ctx.scriptLet.nestBlock(pendingBlock); pendingBlock.children.push(...convertChildren(pending, pendingBlock, ctx)); awaitBlock.pending = pendingBlock; ctx.scriptLet.closeScope(); } const then = getThenFromAwaitBlock(node); if (then) { const awaitThen = !pending; if (awaitThen) { awaitBlock.kind = "await-then"; } const thenStart = awaitBlock.pending ? startBlockIndex(ctx.code, node.value ? getWithLoc(node.value).start : startIndexFromFragment(then, () => { return awaitBlock.pending.range[1]; }), ":then") : nodeStart; const thenBlock = { type: "SvelteAwaitThenBlock", awaitThen, value: null, children: [], parent: awaitBlock, ...ctx.getConvertLocation({ start: thenStart, end: endIndexFromFragment(then, () => { return (ctx.code.indexOf("}", node.value ? getWithLoc(node.value).end : thenStart + 5) + 1); }), }), }; if (node.value) { const baseParam = { node: node.value, parent: thenBlock, callback(value) { thenBlock.value = value; }, typing: "any", }; ctx.scriptLet.nestBlock(thenBlock, (typeCtx) => { if (!typeCtx) { return { param: baseParam, }; } const expression = ctx.getText(node.expression); if (node.expression.type === "Literal") { return { param: { ...baseParam, typing: expression, }, }; } const idAwaitThenValue = typeCtx.generateUniqueId("AwaitThenValue"); if (node.expression.type === "Identifier" && // We cannot use type annotations like `(x: Foo<x>)` if they have the same identifier name. !hasIdentifierFor(node.expression.name, baseParam.node)) { return { preparationScript: [generateAwaitThenValueType(idAwaitThenValue)], param: { ...baseParam, typing: `${idAwaitThenValue}<(typeof ${expression})>`, }, }; } const id = typeCtx.generateUniqueId(expression); return { preparationScript: [ { script: `const ${id} = ${expression};`, nodeType: "VariableDeclaration", }, generateAwaitThenValueType(idAwaitThenValue), ], param: { ...baseParam, typing: `${idAwaitThenValue}<(typeof ${id})>`, }, }; }); } else { ctx.scriptLet.nestBlock(thenBlock); } thenBlock.children.push(...convertChildren(then, thenBlock, ctx)); if (awaitBlock.pending) { extractMustacheBlockTokens(thenBlock, ctx, { startOnly: true }); } else { const thenIndex = ctx.code.indexOf("then", getWithLoc(node.expression).end); ctx.addToken("MustacheKeyword", { start: thenIndex, end: thenIndex + 4, }); } awaitBlock.then = thenBlock; ctx.scriptLet.closeScope(); } const catchFragment = getCatchFromAwaitBlock(node); if (catchFragment) { const awaitCatch = !pending && !then; if (awaitCatch) { awaitBlock.kind = "await-catch"; } const catchStart = awaitBlock.then || awaitBlock.pending ? startBlockIndex(ctx.code, node.error ? getWithLoc(node.error).start : startIndexFromFragment(catchFragment, () => { return (awaitBlock.then || awaitBlock.pending).range[1]; }), ":catch") : nodeStart; const catchBlock = { type: "SvelteAwaitCatchBlock", awaitCatch, error: null, children: [], parent: awaitBlock, ...ctx.getConvertLocation({ start: catchStart, end: endIndexFromFragment(catchFragment, () => { return (ctx.code.indexOf("}", node.error ? getWithLoc(node.error).end : catchStart + 6) + 1); }), }), }; if (node.error) { ctx.scriptLet.nestBlock(catchBlock, [ { node: node.error, parent: catchBlock, typing: "Error", callback: (error) => { catchBlock.error = error; }, }, ]); } else { ctx.scriptLet.nestBlock(catchBlock); } catchBlock.children.push(...convertChildren(catchFragment, catchBlock, ctx)); if (awaitBlock.pending || awaitBlock.then) { extractMustacheBlockTokens(catchBlock, ctx, { startOnly: true }); } else { const catchIndex = ctx.code.indexOf("catch", getWithLoc(node.expression).end); ctx.addToken("MustacheKeyword", { start: catchIndex, end: catchIndex + 5, }); } awaitBlock.catch = catchBlock; ctx.scriptLet.closeScope(); } extractMustacheBlockTokens(awaitBlock, ctx); return awaitBlock; } /** Convert for KeyBlock */ export function convertKeyBlock(node, parent, ctx) { const nodeStart = startBlockIndex(ctx.code, node.start, "#key"); const keyBlock = { type: "SvelteKeyBlock", expression: null, children: [], parent, ...ctx.getConvertLocation({ start: nodeStart, end: node.end }), }; ctx.scriptLet.addExpression(node.expression, keyBlock, null, (expression) => { keyBlock.expression = expression; }); ctx.scriptLet.nestBlock(keyBlock); keyBlock.children.push(...convertChildren({ nodes: // Adjust for Svelte v5 trimChildren(getChildren(getFragment(node))), }, keyBlock, ctx)); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(keyBlock, ctx); return keyBlock; } /** Convert for SnippetBlock */ export function convertSnippetBlock(node, parent, ctx) { // {#snippet x(args)}...{/snippet} const nodeStart = startBlockIndex(ctx.code, node.start, "#snippet"); const snippetBlock = { type: "SvelteSnippetBlock", id: null, params: [], children: [], parent, ...ctx.getConvertLocation({ start: nodeStart, end: node.end }), }; let beforeClosingParen; if (node.parameters.length > 0) { const lastParam = node.parameters[node.parameters.length - 1]; beforeClosingParen = lastParam.typeAnnotation ?? lastParam; } else { beforeClosingParen = node.expression; } const closeParenIndex = ctx.code.indexOf(")", getWithLoc(beforeClosingParen).end); const scopeKind = parent.type === "Program" ? "snippet" : // use currentScriptScopeKind null; ctx.scriptLet.nestSnippetBlock(node.expression, closeParenIndex, snippetBlock, scopeKind, (id, params) => { snippetBlock.id = id; snippetBlock.params = params; }); snippetBlock.children.push(...convertChildren({ nodes: // Adjust for Svelte v5 trimChildren(node.body.nodes), }, snippetBlock, ctx)); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(snippetBlock, ctx); ctx.snippets.push(snippetBlock); return snippetBlock; } /** Extract mustache block tokens */ function extractMustacheBlockTokens(node, ctx, option) { const startSectionNameStart = indexOf(ctx.code, (c) => Boolean(c.trim()), node.range[0] + 1); const startSectionNameEnd = indexOf(ctx.code, (c) => c === "}" || !c.trim(), startSectionNameStart + 1); ctx.addToken("MustacheKeyword", { start: startSectionNameStart, end: startSectionNameEnd, }); if (option?.startOnly) { return; } const endSectionNameEnd = lastIndexOf(ctx.code, (c) => Boolean(c.trim()), node.range[1] - 2) + 1; const endSectionNameStart = lastIndexOf(ctx.code, (c) => c === "{" || c === "/" || !c.trim(), endSectionNameEnd - 1); ctx.addToken("MustacheKeyword", { start: endSectionNameStart, end: endSectionNameEnd, }); } /** Generate Awaited like type code */ function generateAwaitThenValueType(id) { return { script: `type ${id}<T> = T extends null | undefined ? T : T extends { then(value: infer F): any } ? F extends (value: infer V, ...args: any) => any ? ${id}<V> : never : T;`, nodeType: "TSTypeAliasDeclaration", }; } /** Checks whether the given name identifier is exists or not. */ function hasIdentifierFor(name, node) { if (node.type === "Identifier") { return node.name === name; } if (node.type === "ObjectPattern") { return node.properties.some((property) => property.type === "Property" ? hasIdentifierFor(name, property.value) : hasIdentifierFor(name, property)); } if (node.type === "ArrayPattern") { return node.elements.some((element) => element && hasIdentifierFor(name, element)); } if (node.type === "RestElement") { return hasIdentifierFor(name, node.argument); } if (node.type === "AssignmentPattern") { return hasIdentifierFor(name, node.left); } return false; }