UNPKG

svelte-parse

Version:

An increidbly relaxed svelte-parser

1,350 lines (1,162 loc) 29.5 kB
import { Point, Node, BaseSvelteTag, SvelteMeta, Property, SvelteElement, Directive, Text, Literal, Root, SvelteExpression, VoidBlock, BranchingBlock, Branch, Comment, Parent, SvelteComponent, SvelteScript, SvelteStyle, SvelteDynamicContent, } from 'svast'; import { ParseNodeOptions, ParseOptions, Result, State, SLASH, CLOSE_ANGLE_BRACKET, COLON, EQUALS, QUOTE, APOSTROPHE, TAB, CLOSE_BRACE, BACKTICK, BACKSLASH, AT, OCTOTHERP, RE_BLOCK_BRANCH, RE_SCRIPT_STYLE, RE_COMMENT_START, RE_COMMENT_END, RE_END_TAG_START, RE_ONLY_WHITESPACE, } from './types_and_things'; import { LINEFEED, OPEN_ANGLE_BRACKET, OPEN_BRACE, UPPERCASE_A, UPPERCASE_Z, LOWERCASE_A, LOWERCASE_Z, SPACE, PIPE, } from './types_and_things'; import { void_els } from './void_els'; export function parseNode(opts: ParseNodeOptions): Result | undefined { let index = 0; let quote_type = ''; let expr_quote_type = ''; let closing_tag_name = ''; let brace_count = 0; const node_stack: Node[] = []; const state: State[] = []; const { value, block = true, childParser, generatePositions = true } = opts; let position = opts.currentPosition || { line: 1, column: 1, offset: 0, index, }; let char = value.charCodeAt(index); function chomp() { // newline means new line if (char === LINEFEED) { position.line++; position.column = 1; } else { // otherwise shift along the column pointer position.column++; } // refers to the current parse index++; //@ts-ignore position.offset++; // stay in sync position.index = index; char = value.charCodeAt(index); } function place() { return { line: position.line, column: position.column, offset: position.offset, }; } let current_state: State | undefined; function pop_state() { state.pop(); current_state = state[state.length - 1]; } function set_state(name: State, toPop?: boolean) { if (toPop) state.pop(); state.push((current_state = name)); } let current_node: Node | undefined; function push_node(node: Node) { node_stack.push((current_node = node)); } function pop_node() { node_stack.pop(); current_node = node_stack[node_stack.length - 1]; } let _n; let _n2; for (;;) { // console.log(value[index], node_stack, state); if (value[index] === void 0) { if (generatePositions) //@ts-ignore current_node.position.end = place(); break; } // right at the start if (current_state === void 0) { if (RE_BLOCK_BRANCH.test(value.substring(index))) { if (generatePositions && node_stack.length) //@ts-ignore current_node.position.end = place(); return; } if (RE_END_TAG_START.test(value.substring(index))) { return; } if (RE_COMMENT_START.test(value.substring(index))) { _n = <Comment>{ type: 'comment', value: '', }; //@ts-ignore if (generatePositions) _n.position = { start: place(), end: {} }; push_node(_n); set_state(State.IN_COMMENT); chomp(); chomp(); chomp(); chomp(); continue; } // "{" => tag if (char === OPEN_BRACE) { push_node(<SvelteDynamicContent>{ type: 'svelteDynamicContent', }); if (generatePositions) { //@ts-ignore current_node.position = { start: place(), end: {} }; } set_state(State.MAYBE_IN_DYNAMIC_CONTENT); chomp(); continue; } if (char === OPEN_ANGLE_BRACKET) { set_state(State.IN_START_TAG); push_node(<BaseSvelteTag<''>>{ type: '', tagName: '', properties: [], selfClosing: false, children: [], }); if (generatePositions) //@ts-ignore current_node.position = { start: place(), end: {} }; chomp(); continue; } } if (current_state === State.IN_COMMENT) { if (RE_COMMENT_END.test(value.substring(index))) { chomp(); chomp(); chomp(); //@ts-ignore if (generatePositions) current_node.position.end = place(); break; } (current_node as Comment).value += value[index]; chomp(); continue; } if (current_state === State.MAYBE_IN_DYNAMIC_CONTENT) { if (char === SPACE || char === LINEFEED || char === TAB) { chomp(); continue; } if (char === AT) { _n = <VoidBlock>{ type: 'svelteVoidBlock', name: '', expression: { type: 'svelteExpression', value: '', }, }; if (generatePositions) { //@ts-ignore _n.position = Object.assign({}, current_node.position); } pop_node(); push_node(_n); set_state(State.IN_VOID_BLOCK, true); chomp(); continue; } if (char === OCTOTHERP) { set_state(State.IN_BRANCHING_BLOCK, true); set_state(State.IN_BRANCHING_BLOCK_NAME); chomp(); continue; } set_state(State.IN_DYNAMIC_CONTENT, true); continue; } if (current_state === State.IN_DYNAMIC_CONTENT) { if (char === CLOSE_BRACE) { chomp(); if (generatePositions) { //@ts-ignore current_node.position.end = place(); } if (node_stack.length === 1) break; pop_node(); pop_state(); continue; } const n = <SvelteExpression>{ type: 'svelteExpression', value: '', }; (current_node as SvelteDynamicContent).expression = n; push_node(n); if (generatePositions) { //@ts-ignore current_node.position = { start: place(), end: {} }; } set_state(State.IN_EXPRESSION); continue; } if (current_state === State.IN_BRANCHING_BLOCK_NAME) { if (char === CLOSE_BRACE) { // each pop_state(); continue; } if (char === SPACE) { _n = <BranchingBlock>{ type: 'svelteBranchingBlock', name: (current_node as SvelteExpression).value, branches: [], }; _n2 = <Branch>{ type: 'svelteBranch', name: (current_node as SvelteExpression).value, expression: { type: 'svelteExpression', value: '', }, children: [], }; if (generatePositions) { _n.position = Object.assign( {}, (current_node as SvelteExpression).position ); _n2.position = Object.assign( {}, (current_node as SvelteExpression).position ); } pop_node(); push_node(_n); push_node(_n2); push_node(_n2.expression); _n.branches.push(_n2); pop_state(); continue; } if (!(current_node as SvelteExpression).value) { (current_node as SvelteExpression).value = ''; } (current_node as SvelteExpression).value += value[index]; chomp(); continue; } if (current_state === State.IN_BRANCHING_BLOCK_END) { if (char === SPACE || char === LINEFEED || char === TAB) { // ERROR - NAME AFTER CLOSING BLOCK SLASH } if (char === CLOSE_BRACE) { pop_node(); chomp(); if (generatePositions) //@ts-ignore current_node.position.end = place(); if (closing_tag_name !== (current_node as BranchingBlock).name) { // ERROR SHOULD BE A MATCHING NAME (current_node.name) } break; } closing_tag_name += value[index]; chomp(); continue; } if (current_state === State.IN_BRANCHING_BLOCK_BRANCH_NAME) { if ( (char === SPACE && value.substring(index - 4, index + 3) !== 'else if') || char === CLOSE_BRACE ) { _n2 = <Branch>{ type: 'svelteBranch', name: (current_node as SvelteExpression).value, expression: { type: 'svelteExpression', value: '', }, children: [], }; if (generatePositions) { _n2.position = Object.assign( {}, (current_node as SvelteExpression).position ); } pop_node(); pop_node(); (current_node as BranchingBlock).branches.push(_n2); push_node(_n2); push_node(_n2.expression); pop_state(); continue; } (current_node as SvelteExpression).value += value[index]; chomp(); continue; } if (current_state === State.IN_BRANCHING_BLOCK) { if (char === CLOSE_BRACE) { if ((current_node as SvelteExpression).type === 'svelteExpression') pop_node(); chomp(); set_state(State.PARSE_CHILDREN); continue; } if (char === SPACE) { set_state(State.IN_EXPRESSION); chomp(); if (generatePositions) //@ts-ignore current_node.position = { start: place(), end: {} }; continue; } } if (current_state === State.IN_BRANCHING_BLOCK_BRANCH) { if (char === COLON) { set_state(State.IN_BRANCHING_BLOCK_BRANCH_NAME, true); chomp(); continue; } if (char === SLASH) { closing_tag_name = ''; pop_node(); set_state(State.IN_BRANCHING_BLOCK_END, true); chomp(); continue; } if (char === SPACE || char === LINEFEED || char === TAB) { chomp(); continue; } } if (current_state === State.IN_VOID_BLOCK) { if (char === SPACE) { push_node((current_node as VoidBlock).expression); set_state(State.IN_EXPRESSION); chomp(); if (generatePositions) //@ts-ignore current_node.position = { start: place(), end: {} }; continue; } if (char === CLOSE_BRACE) { // if (generatePositions) // //@ts-ignore // (current_node as VoidBlock).expression.position = { // start: place(), // end: place(), // }; chomp(); if (generatePositions) //@ts-ignore current_node.position.end = place(); break; } (current_node as VoidBlock).name += value[index]; chomp(); continue; } if (current_state === State.IN_START_TAG) { if (char === SLASH) return; // lowercase characters for element names if (char >= LOWERCASE_A && char <= LOWERCASE_Z) { (current_node as BaseSvelteTag<'svelteElement'>).type = 'svelteElement'; set_state(State.IN_TAG_NAME); continue; } // uppercase characters for Component names UPPERCASE_A, UPPERCASE_Z if (char >= UPPERCASE_A && char <= UPPERCASE_Z) { (current_node as BaseSvelteTag<'svelteComponent'>).type = 'svelteComponent'; set_state(State.IN_TAG_NAME); continue; } if (char === SPACE || char === TAB || char === LINEFEED) { chomp(); continue; } } // we are inside a tags name if (current_state === State.IN_TAG_NAME) { if ( char === SLASH || (char === CLOSE_ANGLE_BRACKET && //@ts-ignore void_els[current_node.tagName] !== void 0) ) { set_state(State.IN_CLOSING_SLASH, true); (current_node as SvelteElement).selfClosing = true; if (char === SLASH) chomp(); continue; } // space or linefeed put us into the tag body if (char === SPACE || char === TAB || char === LINEFEED) { set_state(State.IN_TAG_BODY, true); chomp(); continue; } if (char === COLON) { (current_node as SvelteMeta).type = 'svelteMeta'; (current_node as SvelteMeta).tagName = ''; chomp(); continue; } if (char === CLOSE_ANGLE_BRACKET) { set_state(State.IN_TAG_BODY, true); continue; } (current_node as SvelteMeta).tagName += value[index]; chomp(); continue; } // we are inside a start tag after the name if (current_state === State.IN_TAG_BODY) { if (char === OPEN_BRACE) { set_state(State.IN_SHORTHAND_ATTR); const _node = <Property>{ type: 'svelteProperty', name: '', value: [ { type: 'svelteDynamicContent', expression: { type: 'svelteExpression', value: '', }, }, ], modifiers: [], shorthand: 'expression', }; (current_node as BaseSvelteTag<''>).properties.push(_node as Property); push_node(_node); if (generatePositions) { //@ts-ignore current_node.position = { start: place(), end: {} }; //@ts-ignore (current_node as Property).value[0].position = { start: place(), //@ts-ignore end: {}, }; } chomp(); if (generatePositions) { //@ts-ignore (current_node as Property).value[0].expression.position = { start: place(), //@ts-ignore end: {}, }; } continue; } // letters mean we've hit an attribute if ( (char >= LOWERCASE_A && char <= LOWERCASE_Z) || (char >= UPPERCASE_A && char <= UPPERCASE_Z) ) { set_state(State.IN_ATTR_NAME); const _node = <Property>{ type: 'svelteProperty', name: '', value: [], modifiers: [], shorthand: 'none', }; (current_node as BaseSvelteTag<''>).properties.push(_node as Property); push_node(_node); if (generatePositions) //@ts-ignore current_node.position = { start: place(), end: {} }; continue; } // "/" or ">" (for void tags) put us in a terminal state if ( char === SLASH || (char === CLOSE_ANGLE_BRACKET && //@ts-ignore void_els[current_node.tagName] !== void 0) ) { set_state(State.IN_CLOSING_SLASH, true); (current_node as BaseSvelteTag<''>).selfClosing = true; if (char === SLASH) chomp(); continue; } if (char === CLOSE_ANGLE_BRACKET) { set_state(State.PARSE_CHILDREN, true); chomp(); //@ts-ignore if (generatePositions) current_node.position.end = place(); continue; } if (char === SPACE || char === TAB || char === LINEFEED) { chomp(); continue; } } if (current_state === State.IN_SHORTHAND_ATTR) { if (char === CLOSE_BRACE) { (current_node as Property).name = ((current_node as Property) .value[0] as SvelteDynamicContent).expression.value; if (generatePositions) { //@ts-ignore current_node.position.end = place(); //@ts-ignore (current_node as Property).value[0].position.end = place(); } pop_state(); pop_node(); chomp(); continue; } push_node( ((current_node as Property).value[0] as SvelteDynamicContent).expression ); set_state(State.IN_EXPRESSION); continue; } // we are expecting the tag to close completely here if (current_state === State.IN_CLOSING_SLASH) { // ignore ws if (char === SPACE || char === TAB || char === LINEFEED) { chomp(); continue; } // we closed successfully, end the parse if (char === CLOSE_ANGLE_BRACKET) { chomp(); // @ts-ignore if (generatePositions) current_node.position.end = place(); break; } // DANGER ZONE - something went wrong } // we are parsing a property name if (current_state === State.IN_ATTR_NAME) { // " ", "\n", "/" or ">" => shorthand boolean attr if ( char === SPACE || char === TAB || char === LINEFEED || char === SLASH || char === CLOSE_ANGLE_BRACKET ) { (current_node as Property).shorthand = 'boolean'; if (generatePositions) //@ts-ignore current_node.position.end = place(); pop_state(); pop_node(); continue; } // ":" => directive if (char === COLON) { //@ts-ignore (current_node as Directive).type = 'svelteDirective'; (current_node as Directive).specifier = ''; set_state(State.IN_DIRECTIVE_SPECIFIER, true); chomp(); continue; } if (char === PIPE) { chomp(); _n = { value: '', type: 'modifier' }; if (generatePositions) //@ts-ignore _n.position = { start: place(), end: [] }; (current_node as Directive).modifiers.push(_n as Literal<'modifier'>); push_node(_n); set_state(State.IN_ATTR_MODIFIER, true); continue; } if (char === EQUALS) { set_state(State.IN_ATTR_VALUE, true); chomp(); continue; } // process the token and chomp, everything is good (current_node as Property).name += value[index]; chomp(); continue; } // att values can be quoted or unquoted if (current_state === State.IN_ATTR_VALUE) { // ignore whitespace it is valid after `=` if (char === SPACE || char === TAB || char === LINEFEED) { chomp(); continue; } // quoted attr if (char === QUOTE || char === APOSTROPHE) { set_state(State.IN_QUOTED_ATTR_VALUE, true); quote_type = value[index]; push_node({ type: 'blank' }); chomp(); continue; } set_state(State.IN_UNQUOTED_ATTR_VALUE, true); continue; } if (current_state === State.IN_UNQUOTED_ATTR_VALUE) { // " ", "\n", "/" or ">" => ends the whole thing if ( char === SPACE || char === TAB || char === LINEFEED || char === CLOSE_ANGLE_BRACKET || /^\/\s*>/.test(value.substring(index)) ) { //@ts-ignore if (current_node.type === 'text') { if (generatePositions) //@ts-ignore current_node.position.end = place(); pop_node(); } pop_state(); if (generatePositions) //@ts-ignore current_node.position.end = place(); pop_node(); continue; } if (char === OPEN_BRACE) { set_state(State.IN_DYNAMIC_CONTENT); const _n = <SvelteDynamicContent>{ type: 'svelteDynamicContent', }; (current_node as Property).value.push(_n); push_node(_n); // current_node. if (generatePositions) { //@ts-ignore current_node.position = { start: place(), end: {} }; } chomp(); continue; } //@ts-ignore if (current_node.type !== 'text') { const _n = <Text>{ type: 'text', value: '', }; (current_node as Property).value.push(_n); push_node(_n); if (generatePositions) { //@ts-ignore current_node.position = { start: place(), end: {} }; } } (current_node as Text).value += value[index]; chomp(); continue; } if (current_state === State.IN_QUOTED_ATTR_VALUE) { // if we meet our matching quote the attribute has ended if (value[index] === quote_type) { chomp(); //@ts-ignore if (generatePositions) current_node.position.end = place(); pop_node(); quote_type = ''; pop_state(); if ( //@ts-ignore current_node.type !== 'svelteElement' && //@ts-ignore current_node.type !== 'svelteComponent' && //@ts-ignore current_node.type !== 'svelteMeta' ) { //@ts-ignore if (generatePositions) current_node.position.end = place(); pop_node(); } continue; } if (char === OPEN_BRACE) { //@ts-ignore if (generatePositions && current_node.type !== 'blank') //@ts-ignore current_node.position.end = place(); //@ts-ignore current_node.type !== 'svelteProperty' && pop_node(); _n = <SvelteDynamicContent>{ type: 'svelteDynamicContent', }; (current_node as Property).value.push(_n as SvelteDynamicContent); if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; push_node(_n); set_state(State.IN_DYNAMIC_CONTENT); chomp(); continue; } if (char === CLOSE_BRACE) { chomp(); continue; } // " ", "\n" => still in the attribute value but make a new node if (char === SPACE || char === TAB || char === LINEFEED) { const _c = current_node as Text | SvelteExpression; if (_c.type === 'text' && RE_ONLY_WHITESPACE.test(_c.value)) { _c.value += value[index]; chomp(); continue; } //@ts-ignore current_node.type !== 'svelteProperty' && pop_node(); _n = { type: 'text', value: value[index] }; if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; (current_node as Property).value.push(_n as Text); push_node(_n); chomp(); continue; } if (value.charCodeAt(index - 1) === CLOSE_BRACE) { //@ts-ignore current_node.type !== 'svelteProperty' && pop_node(); _n = { type: 'text', value: value[index] }; if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; (current_node as Property).value.push(_n as Text); push_node(_n); chomp(); continue; } if ( (current_node as Text).type === 'text' && RE_ONLY_WHITESPACE.test((current_node as Text).value) ) { pop_node(); _n = { type: 'text', value: '' }; if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; (current_node as Property).value.push(_n as Text); push_node(_n); //@ts-ignore } else if (current_node.type === 'blank') { pop_node(); _n = { type: 'text', value: '' }; if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; (current_node as Property).value.push(_n as Text); push_node(_n); } // capture the token otherwise (current_node as Text).value += value[index]; chomp(); continue; } if (current_state === State.IN_DIRECTIVE_SPECIFIER) { if (char === EQUALS) { set_state(State.IN_ATTR_VALUE, true); chomp(); continue; } if (char === PIPE) { _n = { value: '', type: 'modifier' }; if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; (current_node as Directive).modifiers.push(_n as Literal<'modifier'>); push_node(_n); set_state(State.IN_ATTR_MODIFIER, true); chomp(); continue; } // " ", "\n", "/" or ">" => ends the whole thing if ( char === SPACE || char === TAB || char === LINEFEED || char === SLASH || char === CLOSE_ANGLE_BRACKET ) { if (generatePositions) //@ts-ignore current_node.position.end = place(); pop_state(); pop_node(); continue; } (current_node as Directive).specifier += value[index]; chomp(); continue; } if (current_state === State.IN_ATTR_MODIFIER) { if (char === PIPE) { pop_node(); _n = { value: '', type: 'modifier' }; if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; (current_node as Directive).modifiers.push(_n as Literal<'modifier'>); push_node(_n); chomp(); continue; } if (char === EQUALS) { set_state(State.IN_ATTR_VALUE, true); pop_node(); chomp(); continue; } if ( char === SLASH || char === CLOSE_ANGLE_BRACKET || ((value.charCodeAt(index - 1) === SPACE || value.charCodeAt(index - 1) === TAB || value.charCodeAt(index - 1) === LINEFEED) && ((char >= LOWERCASE_A && char <= LOWERCASE_Z) || (char >= UPPERCASE_A && char <= UPPERCASE_Z))) ) { pop_node(); pop_node(); pop_state(); continue; } if (char === SPACE || char === TAB || char === LINEFEED) { chomp(); continue; } (current_node as Literal<'modifier'>).value += value[index]; chomp(); continue; } if (current_state === State.IN_SCRIPT_STYLE) { if (char === OPEN_ANGLE_BRACKET) { if (RE_SCRIPT_STYLE.test(value.substring(index))) { if (generatePositions) //@ts-ignore current_node.position.end = place(); pop_node(); set_state(State.EXPECT_END_OR_BRANCH, true); continue; } } (current_node as SvelteMeta).value += value[index]; chomp(); continue; } if (current_state === State.PARSE_CHILDREN) { if ( (current_node as SvelteElement | SvelteMeta).tagName === 'script' || (current_node as SvelteElement | SvelteMeta).tagName === 'style' ) { //@ts-ignore current_node.type = 'svelteS' + (current_node as SvelteElement | SvelteMeta).tagName.substring(1); _n = { type: 'text', value: '', }; if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; (current_node as SvelteMeta).children.push(_n as Text); push_node(_n); set_state(State.IN_SCRIPT_STYLE, true); continue; } else { const result = childParser({ generatePositions, value: value.substring(index), currentPosition: position, childParser, }); (current_node as Parent).children = result[0]; //@ts-ignore const _index = position.index + result[2]; position = Object.assign({}, result[1]) as Point & { index: number }; position.index = _index; index = position.index; char = value.charCodeAt(index); } set_state(State.EXPECT_END_OR_BRANCH, true); } if (current_state === State.EXPECT_END_OR_BRANCH) { if (RE_BLOCK_BRANCH.test(value.substring(index))) { set_state(State.IN_BRANCHING_BLOCK_BRANCH, true); _n = <Text>{ type: 'text', value: '', }; if (generatePositions) { //@ts-ignore _n.position = { start: place(), end: {} }; } //@ts-ignore if (generatePositions) current_node.position.end = place(); push_node(_n); chomp(); continue; } if (char === OPEN_ANGLE_BRACKET) { chomp(); continue; } if (char === SLASH) { chomp(); continue; } if (char === SPACE) { chomp(); continue; } if (char === CLOSE_ANGLE_BRACKET) { chomp(); if (generatePositions) { //@ts-ignore current_node.position.end = place(); } let current_node_name = closing_tag_name; if ((current_node as Node).type === 'svelteMeta') { current_node_name = current_node_name.replace('svelte:', ''); } if ( current_node_name !== (current_node as SvelteMeta | SvelteComponent | SvelteElement).tagName ) { console.log( `Was expecting a closing tag for ${ (current_node as SvelteMeta | SvelteComponent | SvelteElement) .tagName } but got ${closing_tag_name}`, //@ts-ignore JSON.stringify(current_node.position) ); } break; } closing_tag_name += value[index]; chomp(); continue; } if (current_state === State.IN_TEXT) { if (char === OPEN_ANGLE_BRACKET || char === OPEN_BRACE) { if (generatePositions) //@ts-ignore current_node.position.end = place(); break; } (current_node as Text).value += value[index]; chomp(); continue; } if (current_state === State.IN_EXPRESSION) { if (expr_quote_type === '' && char === CLOSE_BRACE) { if (brace_count === 0) { if (generatePositions) { //@ts-ignore current_node.position.end = place(); } pop_node(); pop_state(); continue; // if ( // node_stack.length === 1 || // node_stack[0].type === 'svelteVoidBlock' // ) { // if (generatePositions && node_stack[0].type === 'svelteVoidBlock') { // //@ts-ignore // `current_node.position.end = place(); // pop_node();` // } // chomp(); // if (generatePositions) { // //@ts-ignore // current_node.position.end = place(); // } // break; // } else if ( // node_stack[node_stack.length - 2].type === 'svelteBranch' // ) { // pop_state(); // if (generatePositions) { // //@ts-ignore // current_node.position.end = place(); // } // continue; // } else { // pop_state(); // chomp(); // if (generatePositions) { // //@ts-ignore // current_node.position.end = place(); // } // continue; // } } brace_count--; } if (expr_quote_type === '' && char === OPEN_BRACE) { brace_count++; } if ( expr_quote_type === '' && (char === APOSTROPHE || char === QUOTE || char === BACKTICK) ) { set_state(State.IN_EXPRESSION_QUOTE); expr_quote_type = value[index]; (current_node as SvelteExpression).value += value[index]; chomp(); continue; } (current_node as SvelteExpression).value += value[index]; chomp(); continue; } if (current_state === State.IN_EXPRESSION_QUOTE) { if ( value[index] === expr_quote_type && value.charCodeAt(index - 1) !== BACKSLASH ) { expr_quote_type = ''; (current_node as SvelteExpression).value += value[index]; chomp(); pop_state(); continue; } (current_node as SvelteExpression).value += value[index]; chomp(); continue; } set_state(State.IN_TEXT); _n = { type: 'text', value: '', }; push_node(_n); if (generatePositions) //@ts-ignore _n.position = { start: place(), end: {} }; } return { chomped: value.substring(0, index), unchomped: value.substring(index), parsed: node_stack[0], position, }; } function parse_siblings(opts: ParseNodeOptions): [Node[], Point, number] { const { value, currentPosition = { line: 1, column: 1, offset: 0, }, // block = true, childParser = parse_siblings, } = opts; const children = []; let unchomped = value; let position: Point & { index?: number } = Object.assign({}, currentPosition); let parsed; let index = 0; let result; for (;;) { result = parseNode({ generatePositions: opts.generatePositions, value: unchomped, currentPosition: position, childParser, }); if (!result) break; position = result.position; unchomped = result.unchomped; parsed = result.parsed; //@ts-ignore index += position.index; children.push(parsed); if (unchomped.trim().length === 0) break; } return [children, position, index]; } const lineFeed = '\n'; const lineBreaksExpression = /\r\n|\r/g; export function parse(opts: ParseOptions): Root { const root = <Root>{ type: 'root', children: parse_siblings({ generatePositions: opts.generatePositions, value: opts.value.replace(lineBreaksExpression, lineFeed), childParser: parse_siblings, })[0], }; // console.log(JSON.stringify(root.children[root.children.length - 1], null, 2)); if (opts.generatePositions) { root.position = { start: { column: 1, line: 1, offset: 0 }, end: Object.assign( {}, //@ts-ignore root.children[root.children.length - 1].position.end ), }; } return root; }