UNPKG

htmljs-parser

Version:

An HTML parser recognizes content and string placeholders and allows JavaScript expressions as attribute values

1,715 lines (1,701 loc) 81.4 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { ErrorCode: () => ErrorCode, TagType: () => TagType, createParser: () => createParser, getLines: () => getLines, getLocation: () => getLocation, getPosition: () => getPosition }); module.exports = __toCommonJS(src_exports); // src/util/constants.ts var ErrorCode = /* @__PURE__ */ ((ErrorCode3) => { ErrorCode3[ErrorCode3["EXTRA_CLOSING_TAG"] = 0] = "EXTRA_CLOSING_TAG"; ErrorCode3[ErrorCode3["INVALID_ATTRIBUTE_ARGUMENT"] = 1] = "INVALID_ATTRIBUTE_ARGUMENT"; ErrorCode3[ErrorCode3["INVALID_ATTRIBUTE_NAME"] = 2] = "INVALID_ATTRIBUTE_NAME"; ErrorCode3[ErrorCode3["INVALID_ATTRIBUTE_VALUE"] = 3] = "INVALID_ATTRIBUTE_VALUE"; ErrorCode3[ErrorCode3["INVALID_CHARACTER"] = 4] = "INVALID_CHARACTER"; ErrorCode3[ErrorCode3["INVALID_CODE_AFTER_SEMICOLON"] = 5] = "INVALID_CODE_AFTER_SEMICOLON"; ErrorCode3[ErrorCode3["INVALID_EXPRESSION"] = 6] = "INVALID_EXPRESSION"; ErrorCode3[ErrorCode3["INVALID_INDENTATION"] = 7] = "INVALID_INDENTATION"; ErrorCode3[ErrorCode3["INVALID_LINE_START"] = 8] = "INVALID_LINE_START"; ErrorCode3[ErrorCode3["INVALID_REGULAR_EXPRESSION"] = 9] = "INVALID_REGULAR_EXPRESSION"; ErrorCode3[ErrorCode3["INVALID_STRING"] = 10] = "INVALID_STRING"; ErrorCode3[ErrorCode3["INVALID_TAG_ARGUMENT"] = 11] = "INVALID_TAG_ARGUMENT"; ErrorCode3[ErrorCode3["INVALID_TAG_SHORTHAND"] = 12] = "INVALID_TAG_SHORTHAND"; ErrorCode3[ErrorCode3["INVALID_TEMPLATE_STRING"] = 13] = "INVALID_TEMPLATE_STRING"; ErrorCode3[ErrorCode3["MALFORMED_CDATA"] = 14] = "MALFORMED_CDATA"; ErrorCode3[ErrorCode3["MALFORMED_CLOSE_TAG"] = 15] = "MALFORMED_CLOSE_TAG"; ErrorCode3[ErrorCode3["MALFORMED_COMMENT"] = 16] = "MALFORMED_COMMENT"; ErrorCode3[ErrorCode3["MALFORMED_DECLARATION"] = 17] = "MALFORMED_DECLARATION"; ErrorCode3[ErrorCode3["MALFORMED_DOCUMENT_TYPE"] = 18] = "MALFORMED_DOCUMENT_TYPE"; ErrorCode3[ErrorCode3["MALFORMED_OPEN_TAG"] = 19] = "MALFORMED_OPEN_TAG"; ErrorCode3[ErrorCode3["MALFORMED_PLACEHOLDER"] = 20] = "MALFORMED_PLACEHOLDER"; ErrorCode3[ErrorCode3["MISMATCHED_CLOSING_TAG"] = 21] = "MISMATCHED_CLOSING_TAG"; ErrorCode3[ErrorCode3["MISSING_END_TAG"] = 22] = "MISSING_END_TAG"; ErrorCode3[ErrorCode3["MISSING_TAG_VARIABLE"] = 23] = "MISSING_TAG_VARIABLE"; ErrorCode3[ErrorCode3["RESERVED_TAG_NAME"] = 24] = "RESERVED_TAG_NAME"; ErrorCode3[ErrorCode3["ROOT_TAG_ONLY"] = 25] = "ROOT_TAG_ONLY"; ErrorCode3[ErrorCode3["INVALID_TAG_PARAMS"] = 26] = "INVALID_TAG_PARAMS"; ErrorCode3[ErrorCode3["INVALID_TAG_TYPES"] = 27] = "INVALID_TAG_TYPES"; ErrorCode3[ErrorCode3["INVALID_ATTR_TYPE_PARAMS"] = 28] = "INVALID_ATTR_TYPE_PARAMS"; return ErrorCode3; })(ErrorCode || {}); var TagType = /* @__PURE__ */ ((TagType2) => { TagType2[TagType2["html"] = 0] = "html"; TagType2[TagType2["text"] = 1] = "text"; TagType2[TagType2["void"] = 2] = "void"; TagType2[TagType2["statement"] = 3] = "statement"; return TagType2; })(TagType || {}); // src/util/util.ts function isWhitespaceCode(code) { return code <= 32 /* SPACE */; } function isIndentCode(code) { return code === 9 /* TAB */ || code === 32 /* SPACE */; } function getLocation(lines, startOffset, endOffset) { const start = getPosition(lines, startOffset); const end = startOffset === endOffset ? start : getPosAfterLine(lines, start.line, endOffset); return { start, end }; } function getPosition(lines, offset) { return getPosAfterLine(lines, 0, offset); } function getLines(src) { const lines = [0]; for (let i = 0; i < src.length; i++) { if (src.charCodeAt(i) === 10 /* NEWLINE */) { lines.push(i + 1); } } return lines; } function htmlEOF() { this.endText(); while (this.activeTag) { if (this.activeTag.concise) { this.closeTagEnd(this.pos, this.pos, void 0); } else { return this.emitError( this.activeTag, 22 /* MISSING_END_TAG */, 'Missing ending "' + this.read(this.activeTag.tagName) + '" tag' ); } } } function matchesCloseAngleBracket(code) { return code === 62 /* CLOSE_ANGLE_BRACKET */; } function matchesCloseParen(code) { return code === 41 /* CLOSE_PAREN */; } function matchesCloseCurlyBrace(code) { return code === 125 /* CLOSE_CURLY_BRACE */; } function matchesPipe(code) { return code === 124 /* PIPE */; } function getPosAfterLine(lines, startLine, index) { let max = lines.length - 1; let line = startLine; while (line < max) { const mid = 1 + line + max >>> 1; if (lines[mid] <= index) { line = mid; } else { max = mid - 1; } } return { line, character: index - lines[line] }; } // src/core/Parser.ts var Parser = class { // Keeps track of line indexes to provide line/column info. constructor(options) { this.options = options; } read(range) { return this.data.slice(range.start, range.end); } positionAt(offset) { return getPosition( this.lines || (this.lines = getLines(this.data)), offset ); } locationAt(range) { return getLocation( this.lines || (this.lines = getLines(this.data)), range.start, range.end ); } enterState(state) { this.activeState = state; return this.activeRange = state.enter.call( this, this.activeRange, this.pos ); } exitState() { const { activeRange, activeState } = this; const parent = this.activeRange = activeRange.parent; this.activeState = parent.state; this.forward = 0; activeRange.end = this.pos; activeState.exit.call(this, activeRange); this.activeState.return.call(this, activeRange, parent); } /** * Compare a position in the source to either another position, or a string. */ matchAtPos(a, b) { const aPos = a.start; const aLen = a.end - aPos; let bPos = 0; let bLen = 0; let bSource = this.data; if (typeof b === "string") { bLen = b.length; bSource = b; } else { bPos = b.start; bLen = b.end - bPos; } if (aLen !== bLen) return false; for (let i = 0; i < aLen; i++) { if (this.data.charAt(aPos + i) !== bSource.charAt(bPos + i)) { return false; } } return true; } matchAnyAtPos(a, list) { for (const item of list) { if (this.matchAtPos(a, item)) return true; } return false; } /** * Look ahead to see if the given str matches the substring sequence * beyond */ lookAheadFor(str, startPos = this.pos + 1) { let i = str.length; if (startPos + i <= this.maxPos) { const { data } = this; for (; i--; ) { if (str[i] !== data[startPos + i]) { return void 0; } } return str; } } lookAtCharCodeAhead(offset, startPos = this.pos) { return this.data.charCodeAt(startPos + offset); } startText() { if (this.textPos === -1) { this.textPos = this.pos; } } endText() { var _a, _b; const start = this.textPos; if (start !== -1) { (_b = (_a = this.options).onText) == null ? void 0 : _b.call(_a, { start, end: this.pos }); this.textPos = -1; } } /** * This is used to enter into "HTML" parsing mode instead * of concise HTML. We push a block on to the stack so that we know when * return back to the previous parsing mode and to ensure that all * tags within a block are properly closed. */ beginHtmlBlock(delimiter, singleLine) { var _a; const content = this.enterState( ((_a = this.activeTag) == null ? void 0 : _a.type) === 1 /* text */ ? states_exports.PARSED_TEXT_CONTENT : states_exports.HTML_CONTENT ); content.singleLine = singleLine; content.delimiter = delimiter; content.indent = this.indent; } emitError(range, code, message) { var _a, _b; let start, end; if (typeof range === "number") { start = end = range; } else { start = range.start; end = range.end; } (_b = (_a = this.options).onError) == null ? void 0 : _b.call(_a, { start, end, code, message }); this.pos = this.maxPos + 1; } closeTagEnd(start, end, name) { var _a, _b, _c, _d; const { beginMixedMode, parentTag } = this.activeTag; if (beginMixedMode) this.endingMixedModeAtEOL = true; this.activeTag = parentTag; if (name) (_b = (_a = this.options).onCloseTagName) == null ? void 0 : _b.call(_a, name); (_d = (_c = this.options).onCloseTagEnd) == null ? void 0 : _d.call(_c, { start, end }); } // -------------------------- consumeWhitespaceIfBefore(str, start = 0) { const { pos, data } = this; let cur = pos + start; while (isWhitespaceCode(data.charCodeAt(cur))) cur++; if (this.lookAheadFor(str, cur)) { this.pos = cur; if (this.forward > 1) this.forward = 1; return true; } return false; } getPreviousNonWhitespaceCharCode(start = -1) { let behind = start; while (isWhitespaceCode(this.lookAtCharCodeAhead(behind))) behind--; return this.lookAtCharCodeAhead(behind); } onlyWhitespaceRemainsOnLine(start = 1) { const maxOffset = this.maxPos - this.pos; let ahead = start; while (ahead < maxOffset) { const code = this.lookAtCharCodeAhead(ahead); if (isWhitespaceCode(code)) { switch (code) { case 13 /* CARRIAGE_RETURN */: case 10 /* NEWLINE */: return true; } } else { return false; } ahead++; } return true; } consumeWhitespaceOnLine(start = 1) { const maxOffset = this.maxPos - this.pos; let ahead = start; while (ahead < maxOffset) { const code = this.lookAtCharCodeAhead(ahead); if (isWhitespaceCode(code)) { switch (code) { case 13 /* CARRIAGE_RETURN */: case 10 /* NEWLINE */: this.pos += ahead; return true; } } else { this.pos += ahead; return false; } ahead++; } this.pos = this.maxPos; return true; } consumeWhitespace() { const maxOffset = this.maxPos - this.pos; let ahead = 0; while (ahead < maxOffset && isWhitespaceCode(this.lookAtCharCodeAhead(ahead))) { ahead++; } this.pos += ahead; } parse(data) { const maxPos = this.maxPos = data.length; this.data = data; this.indent = ""; this.textPos = -1; this.forward = 1; this.isConcise = true; this.beginMixedMode = this.endingMixedModeAtEOL = false; this.lines = this.activeTag = this.activeAttr = void 0; this.pos = data.charCodeAt(0) === 65279 ? 1 : 0; this.enterState(states_exports.CONCISE_HTML_CONTENT); while (this.pos < maxPos) { const code = data.charCodeAt(this.pos); if (code === 10 /* NEWLINE */) { this.forward = 1; this.activeState.eol.call(this, 1, this.activeRange); } else if (code === 13 /* CARRIAGE_RETURN */ && data.charCodeAt(this.pos + 1) === 10 /* NEWLINE */) { this.forward = 2; this.activeState.eol.call(this, 2, this.activeRange); } else { this.forward = 1; this.activeState.char.call(this, code, this.activeRange); } this.pos += this.forward; } while (this.pos === this.maxPos) { this.forward = 1; this.activeState.eof.call(this, this.activeRange); if (this.forward !== 0) break; } } }; // src/states/index.ts var states_exports = {}; __export(states_exports, { ATTRIBUTE: () => ATTRIBUTE, BEGIN_DELIMITED_HTML_BLOCK: () => BEGIN_DELIMITED_HTML_BLOCK, CDATA: () => CDATA, CLOSE_TAG: () => CLOSE_TAG, CONCISE_HTML_CONTENT: () => CONCISE_HTML_CONTENT, DECLARATION: () => DECLARATION, DTD: () => DTD, EXPRESSION: () => EXPRESSION, HTML_COMMENT: () => HTML_COMMENT, HTML_CONTENT: () => HTML_CONTENT, INLINE_SCRIPT: () => INLINE_SCRIPT, JS_COMMENT_BLOCK: () => JS_COMMENT_BLOCK, JS_COMMENT_LINE: () => JS_COMMENT_LINE, OPEN_TAG: () => OPEN_TAG, PARSED_STRING: () => PARSED_STRING, PARSED_TEXT_CONTENT: () => PARSED_TEXT_CONTENT, PLACEHOLDER: () => PLACEHOLDER, REGULAR_EXPRESSION: () => REGULAR_EXPRESSION, STRING: () => STRING, TAG_NAME: () => TAG_NAME, TAG_STAGE: () => TAG_STAGE, TEMPLATE_STRING: () => TEMPLATE_STRING, checkForCDATA: () => checkForCDATA, checkForClosingTag: () => checkForClosingTag, checkForPlaceholder: () => checkForPlaceholder, handleDelimitedEOL: () => handleDelimitedEOL }); // src/states/OPEN_TAG.ts var TAG_STAGE = /* @__PURE__ */ ((TAG_STAGE2) => { TAG_STAGE2[TAG_STAGE2["UNKNOWN"] = 0] = "UNKNOWN"; TAG_STAGE2[TAG_STAGE2["VAR"] = 1] = "VAR"; TAG_STAGE2[TAG_STAGE2["ARGUMENT"] = 2] = "ARGUMENT"; TAG_STAGE2[TAG_STAGE2["TYPES"] = 3] = "TYPES"; TAG_STAGE2[TAG_STAGE2["PARAMS"] = 4] = "PARAMS"; TAG_STAGE2[TAG_STAGE2["ATTR_GROUP"] = 5] = "ATTR_GROUP"; return TAG_STAGE2; })(TAG_STAGE || {}); var OPEN_TAG = { name: "OPEN_TAG", enter(parent, start) { const tag = this.activeTag = { state: OPEN_TAG, type: 0 /* html */, parent, start, end: start, stage: 0 /* UNKNOWN */, parentTag: this.activeTag, nestedIndent: void 0, indent: this.indent, hasShorthandId: false, hasArgs: false, hasAttrs: false, hasParams: false, typeParams: void 0, selfClosed: false, shorthandEnd: -1, tagName: void 0, concise: this.isConcise, beginMixedMode: this.beginMixedMode || this.endingMixedModeAtEOL }; this.beginMixedMode = false; this.endingMixedModeAtEOL = false; this.endText(); return tag; }, exit(tag) { var _a, _b; const { selfClosed } = tag; (_b = (_a = this.options).onOpenTagEnd) == null ? void 0 : _b.call(_a, { start: this.pos - (this.isConcise ? 0 : selfClosed ? 2 : 1), end: this.pos, selfClosed }); switch (selfClosed ? 2 /* void */ : tag.type) { case 2 /* void */: case 3 /* statement */: { if (tag.beginMixedMode) this.endingMixedModeAtEOL = true; this.activeTag = tag.parentTag; break; } case 1 /* text */: if (this.isConcise) { this.enterState(states_exports.CONCISE_HTML_CONTENT); } else { this.enterState(states_exports.PARSED_TEXT_CONTENT); } break; } }, eol(_, tag) { if (this.isConcise && tag.stage !== 5 /* ATTR_GROUP */ && !this.consumeWhitespaceIfBefore(",")) { this.exitState(); } }, eof(tag) { if (this.isConcise) { if (tag.stage === 5 /* ATTR_GROUP */) { this.emitError( tag, 19 /* MALFORMED_OPEN_TAG */, 'EOF reached while within an attribute group (e.g. "[ ... ]").' ); return; } this.exitState(); } else { this.emitError( tag, 19 /* MALFORMED_OPEN_TAG */, "EOF reached while parsing open tag" ); } }, char(code, tag) { if (this.isConcise) { if (code === 59 /* SEMICOLON */) { this.pos++; this.exitState(); if (!this.consumeWhitespaceOnLine(0)) { switch (this.lookAtCharCodeAhead(0)) { case 47 /* FORWARD_SLASH */: switch (this.lookAtCharCodeAhead(1)) { case 47 /* FORWARD_SLASH */: this.enterState(states_exports.JS_COMMENT_LINE); this.pos += 2; return; case 42 /* ASTERISK */: this.enterState(states_exports.JS_COMMENT_BLOCK); this.pos += 2; return; } break; case 60 /* OPEN_ANGLE_BRACKET */: if (this.lookAheadFor("!--")) { this.enterState(states_exports.HTML_COMMENT); this.pos += 4; return; } break; } this.emitError( this.pos, 5 /* INVALID_CODE_AFTER_SEMICOLON */, "A semicolon indicates the end of a line. Only comments may follow it." ); } return; } if (code === 45 /* HYPHEN */) { if (this.lookAtCharCodeAhead(1) !== 45 /* HYPHEN */) { this.emitError( tag, 19 /* MALFORMED_OPEN_TAG */, '"-" not allowed as first character of attribute name' ); return; } if (tag.stage === 5 /* ATTR_GROUP */) { this.emitError( this.pos, 19 /* MALFORMED_OPEN_TAG */, "Attribute group was not properly ended" ); return; } this.exitState(); const maxPos = this.maxPos; let curPos = this.pos + 1; while (curPos < maxPos && this.data.charCodeAt(++curPos) !== 10 /* NEWLINE */) ; const indentStart = ++curPos; while (curPos < maxPos) { if (isIndentCode(this.data.charCodeAt(curPos))) { curPos++; } else { break; } } const indentSize = curPos - indentStart; if (indentSize > this.indent.length) { this.indent = this.data.slice(indentStart, curPos); } this.enterState(states_exports.BEGIN_DELIMITED_HTML_BLOCK); return; } else if (code === 91 /* OPEN_SQUARE_BRACKET */) { if (tag.stage === 5 /* ATTR_GROUP */) { this.emitError( this.pos, 19 /* MALFORMED_OPEN_TAG */, 'Unexpected "[" character within open tag.' ); return; } tag.stage = 5 /* ATTR_GROUP */; return; } else if (code === 93 /* CLOSE_SQUARE_BRACKET */) { if (tag.stage !== 5 /* ATTR_GROUP */) { this.emitError( this.pos, 19 /* MALFORMED_OPEN_TAG */, 'Unexpected "]" character within open tag.' ); return; } tag.stage = 0 /* UNKNOWN */; return; } } else if (code === 62 /* CLOSE_ANGLE_BRACKET */) { this.pos++; this.exitState(); return; } else if (code === 47 /* FORWARD_SLASH */ && this.lookAtCharCodeAhead(1) === 62 /* CLOSE_ANGLE_BRACKET */) { tag.selfClosed = true; this.pos += 2; this.exitState(); return; } if (code === 47 /* FORWARD_SLASH */) { switch (this.lookAtCharCodeAhead(1)) { case 47 /* FORWARD_SLASH */: this.enterState(states_exports.JS_COMMENT_LINE); this.pos++; return; case 42 /* ASTERISK */: this.enterState(states_exports.JS_COMMENT_BLOCK); this.pos++; return; } } if (isWhitespaceCode(code)) { } else if (code === 44 /* COMMA */) { this.pos++; this.forward = 0; this.consumeWhitespace(); } else { this.forward = 0; if (tag.hasAttrs) { this.enterState(states_exports.ATTRIBUTE); } else if (tag.tagName) { switch (code) { case 47 /* FORWARD_SLASH */: { tag.stage = 1 /* VAR */; this.pos++; if (isWhitespaceCode(this.lookAtCharCodeAhead(0))) { return this.emitError( this.pos, 23 /* MISSING_TAG_VARIABLE */, "A slash was found that was not followed by a variable name or lhs expression" ); } const expr = this.enterState(states_exports.EXPRESSION); expr.operators = true; expr.terminatedByWhitespace = true; expr.shouldTerminate = this.isConcise ? shouldTerminateConciseTagVar : shouldTerminateHtmlTagVar; break; } case 40 /* OPEN_PAREN */: if (tag.hasArgs) { this.emitError( this.pos, 11 /* INVALID_TAG_ARGUMENT */, "A tag can only have one argument" ); return; } tag.hasArgs = true; tag.stage = 2 /* ARGUMENT */; this.pos++; this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseParen; break; case 124 /* PIPE */: if (tag.hasParams) { this.emitError( this.pos, 26 /* INVALID_TAG_PARAMS */, "A tag can only specify parameters once" ); return; } tag.hasParams = true; tag.stage = 4 /* PARAMS */; this.pos++; this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesPipe; break; case 60 /* OPEN_ANGLE_BRACKET */: { tag.stage = 3 /* TYPES */; this.pos++; const expr = this.enterState(states_exports.EXPRESSION); expr.inType = true; expr.forceType = true; expr.shouldTerminate = matchesCloseAngleBracket; break; } default: tag.hasAttrs = true; this.enterState(states_exports.ATTRIBUTE); } } else { this.enterState(states_exports.TAG_NAME); } } }, return(child, tag) { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; if (child.state !== states_exports.EXPRESSION) return; switch (tag.stage) { case 1 /* VAR */: { if (child.start === child.end) { return this.emitError( child, 23 /* MISSING_TAG_VARIABLE */, "A slash was found that was not followed by a variable name or lhs expression" ); } (_b = (_a = this.options).onTagVar) == null ? void 0 : _b.call(_a, { start: child.start - 1, // include /, end: child.end, value: { start: child.start, end: child.end } }); break; } case 2 /* ARGUMENT */: { const { typeParams } = tag; const start = child.start - 1; const end = ++this.pos; const value = { start: child.start, end: child.end }; if (this.consumeWhitespaceIfBefore("{")) { const attr = this.enterState(states_exports.ATTRIBUTE); if (typeParams) { attr.start = typeParams.start; attr.typeParams = typeParams; } else { attr.start = start; } attr.args = { start, end, value }; this.forward = 0; tag.hasAttrs = true; } else { if (typeParams) { this.emitError( child, 27 /* INVALID_TAG_TYPES */, "Unexpected types. Type arguments must directly follow a tag name and type paremeters must precede a method or tag parameters." ); break; } (_d = (_c = this.options).onTagArgs) == null ? void 0 : _d.call(_c, { start, end, value }); } break; } case 3 /* TYPES */: { const { typeParams, hasParams, hasArgs } = tag; const end = ++this.pos; const types = { start: child.start - 1, // include < end, value: { start: child.start, end: child.end } }; if (tag.tagName.end === types.start) { (_f = (_e = this.options).onTagTypeArgs) == null ? void 0 : _f.call(_e, types); break; } this.consumeWhitespace(); const nextCode = this.lookAtCharCodeAhead(0); if (nextCode === 124 /* PIPE */ && !hasParams) { (_h = (_g = this.options).onTagTypeParams) == null ? void 0 : _h.call(_g, types); } else if (nextCode === 40 /* OPEN_PAREN */ && !(typeParams || hasParams || hasArgs)) { tag.typeParams = types; } else { this.emitError( child, 27 /* INVALID_TAG_TYPES */, "Unexpected types. Type arguments must directly follow a tag name and type paremeters must precede a method or tag parameters." ); } break; } case 4 /* PARAMS */: { const end = ++this.pos; (_j = (_i = this.options).onTagParams) == null ? void 0 : _j.call(_i, { start: child.start - 1, end, value: { start: child.start, end: child.end } }); break; } } } }; function shouldTerminateConciseTagVar(code, data, pos, expression) { switch (code) { case 44 /* COMMA */: case 61 /* EQUAL */: case 124 /* PIPE */: case 40 /* OPEN_PAREN */: case 59 /* SEMICOLON */: return true; case 60 /* OPEN_ANGLE_BRACKET */: return !expression.inType; case 45 /* HYPHEN */: return data.charCodeAt(pos + 1) === 45 /* HYPHEN */; case 58 /* COLON */: return data.charCodeAt(pos + 1) === 61 /* EQUAL */; default: return false; } } function shouldTerminateHtmlTagVar(code, data, pos, expression) { switch (code) { case 124 /* PIPE */: case 44 /* COMMA */: case 61 /* EQUAL */: case 40 /* OPEN_PAREN */: case 62 /* CLOSE_ANGLE_BRACKET */: return true; case 60 /* OPEN_ANGLE_BRACKET */: return !expression.inType; case 58 /* COLON */: return data.charCodeAt(pos + 1) === 61 /* EQUAL */; case 47 /* FORWARD_SLASH */: return data.charCodeAt(pos + 1) === 62 /* CLOSE_ANGLE_BRACKET */; default: return false; } } // src/states/ATTRIBUTE.ts var ATTRIBUTE = { name: "ATTRIBUTE", enter(parent, start) { return this.activeAttr = { state: ATTRIBUTE, parent, start, end: start, valueStart: start, stage: 0 /* UNKNOWN */, name: void 0, args: false, typeParams: void 0, bound: false, spread: false }; }, exit() { this.activeAttr = void 0; }, char(code, attr) { if (isWhitespaceCode(code)) { return; } else if (code === 61 /* EQUAL */ || code === 58 /* COLON */ && this.lookAtCharCodeAhead(1) === 61 /* EQUAL */ || code === 46 /* PERIOD */ && this.lookAheadFor("..")) { attr.valueStart = this.pos; this.forward = 0; if (code === 58 /* COLON */) { ensureAttrName(this, attr); attr.bound = true; this.pos += 2; this.consumeWhitespace(); } else if (code === 46 /* PERIOD */) { attr.spread = true; this.pos += 3; } else { ensureAttrName(this, attr); this.pos++; this.consumeWhitespace(); } attr.stage = 2 /* VALUE */; const expr = this.enterState(states_exports.EXPRESSION); expr.operators = true; expr.terminatedByWhitespace = true; expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 5 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrValue : shouldTerminateConciseAttrValue : shouldTerminateHtmlAttrValue; } else if (code === 40 /* OPEN_PAREN */) { ensureAttrName(this, attr); attr.stage = 3 /* ARGUMENT */; this.pos++; this.forward = 0; this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseParen; } else if (code === 60 /* OPEN_ANGLE_BRACKET */ && attr.stage === 1 /* NAME */) { attr.stage = 4 /* TYPE_PARAMS */; this.pos++; this.forward = 0; const expr = this.enterState(states_exports.EXPRESSION); expr.inType = true; expr.forceType = true; expr.shouldTerminate = matchesCloseAngleBracket; } else if (code === 123 /* OPEN_CURLY_BRACE */ && attr.args) { ensureAttrName(this, attr); attr.stage = 5 /* BLOCK */; this.pos++; this.forward = 0; this.enterState(states_exports.EXPRESSION).shouldTerminate = matchesCloseCurlyBrace; } else if (attr.stage === 0 /* UNKNOWN */) { if (code === 60 /* OPEN_ANGLE_BRACKET */) { return this.emitError( this.pos, 2 /* INVALID_ATTRIBUTE_NAME */, 'Invalid attribute name. Attribute name cannot begin with the "<" character.' ); } attr.stage = 1 /* NAME */; this.forward = 0; const expr = this.enterState(states_exports.EXPRESSION); expr.terminatedByWhitespace = true; expr.shouldTerminate = this.isConcise ? this.activeTag.stage === 5 /* ATTR_GROUP */ ? shouldTerminateConciseGroupedAttrName : shouldTerminateConciseAttrName : shouldTerminateHtmlAttrName; } else { this.exitState(); } }, eol() { if (this.isConcise) { this.exitState(); } }, eof(attr) { if (this.isConcise) { this.exitState(); } else { this.emitError( attr, 19 /* MALFORMED_OPEN_TAG */, 'EOF reached while parsing attribute "' + (attr.name ? this.read(attr.name) : "default") + '" for the "' + this.read(this.activeTag.tagName) + '" tag' ); } }, return(child, attr) { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; switch (attr.stage) { case 1 /* NAME */: { attr.name = { start: child.start, end: child.end }; (_b = (_a = this.options).onAttrName) == null ? void 0 : _b.call(_a, attr.name); break; } case 3 /* ARGUMENT */: { if (attr.args) { this.emitError( child, 1 /* INVALID_ATTRIBUTE_ARGUMENT */, "An attribute can only have one set of arguments" ); return; } const start = child.start - 1; const end = ++this.pos; const value = { start: child.start, end: child.end }; if (this.consumeWhitespaceIfBefore("{")) { attr.args = { start, end, value }; } else if (attr.typeParams) { this.emitError( child, 1 /* INVALID_ATTRIBUTE_ARGUMENT */, "An attribute cannot have both type parameters and arguments" ); } else { attr.args = true; (_d = (_c = this.options).onAttrArgs) == null ? void 0 : _d.call(_c, { start, end, value }); } break; } case 5 /* BLOCK */: { const params = attr.args; const end = ++this.pos; const { typeParams } = attr; const start = typeParams ? typeParams.start : params.start; (_f = (_e = this.options).onAttrMethod) == null ? void 0 : _f.call(_e, { start, end, params, typeParams, body: { start: child.start - 1, // include { end, value: { start: child.start, end: child.end } } }); this.exitState(); break; } case 4 /* TYPE_PARAMS */: { const start = child.start - 1; const end = ++this.pos; if (!this.consumeWhitespaceIfBefore("(")) { return this.emitError( child, 28 /* INVALID_ATTR_TYPE_PARAMS */, "Attribute cannot contain type parameters unless it is a shorthand method" ); } attr.typeParams = { start, end, value: { start: child.start, end: child.end } }; break; } case 2 /* VALUE */: { if (child.start === child.end) { return this.emitError( child, 3 /* INVALID_ATTRIBUTE_VALUE */, "Missing value for attribute" ); } if (attr.spread) { (_h = (_g = this.options).onAttrSpread) == null ? void 0 : _h.call(_g, { start: attr.valueStart, end: child.end, value: { start: child.start, end: child.end } }); } else { (_j = (_i = this.options).onAttrValue) == null ? void 0 : _j.call(_i, { start: attr.valueStart, end: child.end, bound: attr.bound, value: { start: child.start, end: child.end } }); } this.exitState(); break; } } } }; function ensureAttrName(parser, attr) { var _a, _b; if (!attr.name) { (_b = (_a = parser.options).onAttrName) == null ? void 0 : _b.call(_a, { start: attr.start, end: attr.start }); } } function shouldTerminateHtmlAttrName(code, data, pos) { switch (code) { case 44 /* COMMA */: case 61 /* EQUAL */: case 40 /* OPEN_PAREN */: case 62 /* CLOSE_ANGLE_BRACKET */: case 60 /* OPEN_ANGLE_BRACKET */: return true; case 58 /* COLON */: return data.charCodeAt(pos + 1) === 61 /* EQUAL */; case 47 /* FORWARD_SLASH */: return data.charCodeAt(pos + 1) === 62 /* CLOSE_ANGLE_BRACKET */; default: return false; } } function shouldTerminateHtmlAttrValue(code, data, pos) { switch (code) { case 44 /* COMMA */: return true; case 47 /* FORWARD_SLASH */: return data.charCodeAt(pos + 1) === 62 /* CLOSE_ANGLE_BRACKET */; case 62 /* CLOSE_ANGLE_BRACKET */: return pos === this.start || data.charCodeAt(pos - 1) !== 61 /* EQUAL */; default: return false; } } function shouldTerminateConciseAttrName(code, data, pos) { switch (code) { case 44 /* COMMA */: case 61 /* EQUAL */: case 40 /* OPEN_PAREN */: case 59 /* SEMICOLON */: case 60 /* OPEN_ANGLE_BRACKET */: return true; case 58 /* COLON */: return data.charCodeAt(pos + 1) === 61 /* EQUAL */; case 45 /* HYPHEN */: return data.charCodeAt(pos + 1) === 45 /* HYPHEN */ && isWhitespaceCode(data.charCodeAt(pos - 1)); default: return false; } } function shouldTerminateConciseAttrValue(code, data, pos) { switch (code) { case 44 /* COMMA */: case 59 /* SEMICOLON */: return true; case 45 /* HYPHEN */: return data.charCodeAt(pos + 1) === 45 /* HYPHEN */ && isWhitespaceCode(data.charCodeAt(pos - 1)); default: return false; } } function shouldTerminateConciseGroupedAttrName(code, data, pos) { switch (code) { case 44 /* COMMA */: case 61 /* EQUAL */: case 40 /* OPEN_PAREN */: case 93 /* CLOSE_SQUARE_BRACKET */: case 60 /* OPEN_ANGLE_BRACKET */: return true; case 58 /* COLON */: return data.charCodeAt(pos + 1) === 61 /* EQUAL */; default: return false; } } function shouldTerminateConciseGroupedAttrValue(code) { switch (code) { case 44 /* COMMA */: case 93 /* CLOSE_SQUARE_BRACKET */: return true; default: return false; } } // src/states/BEGIN_DELIMITED_HTML_BLOCK.ts var BEGIN_DELIMITED_HTML_BLOCK = { name: "BEGIN_DELIMITED_HTML_BLOCK", enter(parent, start) { return { state: BEGIN_DELIMITED_HTML_BLOCK, parent, start, end: start, indent: this.indent, delimiter: "" }; }, exit() { }, char(code, block) { if (code === 45 /* HYPHEN */) { block.delimiter += "-"; } else { const startPos = this.pos; if (!this.consumeWhitespaceOnLine()) { this.pos = startPos + 1; this.forward = 0; this.beginHtmlBlock(void 0, true); } } }, eol(len, block) { this.beginHtmlBlock(block.delimiter, false); handleDelimitedBlockEOL(this, len, block); }, eof: htmlEOF, return() { } }; function handleDelimitedEOL(parser, newLineLength, content) { if (content.singleLine) { parser.endText(); parser.exitState(); parser.exitState(); return true; } if (content.delimiter) { handleDelimitedBlockEOL(parser, newLineLength, content); return true; } return false; } function handleDelimitedBlockEOL(parser, newLineLength, { indent, delimiter }) { const endHtmlBlockLookahead = indent + delimiter; if (parser.lookAheadFor(endHtmlBlockLookahead, parser.pos + newLineLength)) { parser.startText(); parser.pos += newLineLength; parser.endText(); parser.pos += endHtmlBlockLookahead.length; if (parser.consumeWhitespaceOnLine(0)) { parser.exitState(); parser.exitState(); } else { parser.emitError( parser.pos, 4 /* INVALID_CHARACTER */, "A concise mode closing block delimiter can only be followed by whitespace." ); } } else if (parser.lookAheadFor(indent, parser.pos + newLineLength)) { parser.startText(); parser.pos += indent.length; } else if (indent && !parser.onlyWhitespaceRemainsOnLine(newLineLength)) { parser.endText(); parser.exitState(); parser.exitState(); } else if (parser.pos + newLineLength !== parser.maxPos) { parser.startText(); } } // src/states/CDATA.ts var CDATA = { name: "CDATA", enter(parent, start) { return { state: CDATA, parent, start, end: start }; }, exit(cdata) { var _a, _b; (_b = (_a = this.options).onCDATA) == null ? void 0 : _b.call(_a, { start: cdata.start, end: cdata.end, value: { start: cdata.start + 9, // strip <![CDATA[ end: cdata.end - 3 // strip ]]> } }); }, char(code) { if (code === 93 /* CLOSE_SQUARE_BRACKET */ && this.lookAheadFor("]>")) { this.pos += 3; this.exitState(); return; } }, eol() { }, eof(cdata) { this.emitError( cdata, 14 /* MALFORMED_CDATA */, "EOF reached while parsing CDATA" ); }, return() { } }; function checkForCDATA(parser) { if (parser.lookAheadFor("![CDATA[")) { parser.endText(); parser.enterState(CDATA); parser.pos += 8; return true; } return false; } // src/states/CLOSE_TAG.ts var CLOSE_TAG = { name: "CLOSE_TAG", enter(parent, start) { this.endText(); return { state: CLOSE_TAG, parent, start, end: start }; }, exit() { }, char(code, closeTag) { if (code === 62 /* CLOSE_ANGLE_BRACKET */) { this.pos++; this.exitState(); ensureExpectedCloseTag(this, closeTag); } }, eol() { }, eof(closeTag) { this.emitError( closeTag, 15 /* MALFORMED_CLOSE_TAG */, "EOF reached while parsing closing tag" ); }, return() { } }; function checkForClosingTag(parser) { var _a, _b; const curPos = parser.pos + 1; let match = !!parser.lookAheadFor("/>"); let skip = 3; if (!match) { const { tagName } = parser.activeTag; const tagNameLen = tagName.end - tagName.start; if (tagNameLen) { skip += tagNameLen; match = parser.lookAheadFor("/", curPos) && parser.lookAheadFor(">", 1 + curPos + tagNameLen) && parser.matchAtPos(tagName, { start: 1 + curPos, end: 1 + curPos + tagNameLen }) || false; } } if (match) { parser.endText(); (_b = (_a = parser.options).onCloseTagStart) == null ? void 0 : _b.call(_a, { start: curPos - 1, end: curPos + 1 }); if (ensureExpectedCloseTag(parser, { start: parser.pos, end: parser.pos += skip })) { parser.exitState(); } return true; } return false; } function ensureExpectedCloseTag(parser, closeTag) { const activeTag = parser.activeTag; const closeTagNameStart = closeTag.start + 2; const closeTagNameEnd = closeTag.end - 1; if (!activeTag) { parser.emitError( closeTag, 0 /* EXTRA_CLOSING_TAG */, 'The closing "' + parser.read({ start: closeTagNameStart, end: closeTagNameEnd }) + '" tag was not expected' ); return false; } const closeTagNamePos = { start: closeTagNameStart, end: closeTagNameEnd }; if (closeTagNameStart < closeTagNameEnd) { if (!parser.matchAtPos( closeTagNamePos, activeTag.tagName.end > activeTag.tagName.start ? activeTag.tagName : "div" )) { if (activeTag.shorthandEnd === void 0 || !parser.matchAtPos(closeTagNamePos, { start: activeTag.tagName.start, end: activeTag.shorthandEnd })) { parser.emitError( closeTag, 21 /* MISMATCHED_CLOSING_TAG */, 'The closing "' + parser.read(closeTagNamePos) + '" tag does not match the corresponding opening "' + (parser.read(activeTag.tagName) || "div") + '" tag' ); return false; } } } parser.closeTagEnd(closeTagNameEnd, closeTag.end, closeTagNamePos); return true; } // src/states/CONCISE_HTML_CONTENT.ts var CONCISE_HTML_CONTENT = { name: "CONCISE_HTML_CONTENT", enter(parent, start) { this.isConcise = true; this.indent = ""; return { state: CONCISE_HTML_CONTENT, parent, start, end: start }; }, exit() { }, char(code) { if (isWhitespaceCode(code)) { this.indent += this.data[this.pos]; } else { const curIndent = this.indent.length; const indentStart = this.pos - curIndent - 1; let parentTag = this.activeTag; while (parentTag && parentTag.indent.length >= curIndent) { this.closeTagEnd(indentStart, indentStart, void 0); parentTag = this.activeTag; } if (!parentTag && curIndent) { if (code !== 47 /* FORWARD_SLASH */) { this.emitError( this.pos, 7 /* INVALID_INDENTATION */, "Line has extra indentation at the beginning" ); return; } } if (parentTag) { if (parentTag.type === 1 /* text */ && code !== 45 /* HYPHEN */) { this.emitError( this.pos, 8 /* INVALID_LINE_START */, 'A line within a tag that only allows text content must begin with a "-" character' ); return; } if (parentTag.nestedIndent === void 0) { parentTag.nestedIndent = this.indent; } else if (parentTag.nestedIndent !== this.indent) { this.emitError( this.pos, 7 /* INVALID_INDENTATION */, "Line indentation does match indentation of previous line" ); return; } } switch (code) { case 60 /* OPEN_ANGLE_BRACKET */: this.beginMixedMode = true; this.pos--; this.beginHtmlBlock(void 0, false); return; case 36 /* DOLLAR */: if (isWhitespaceCode(this.lookAtCharCodeAhead(1))) { this.pos++; this.enterState(states_exports.INLINE_SCRIPT); return; } break; case 45 /* HYPHEN */: if (this.lookAtCharCodeAhead(1) === 45 /* HYPHEN */) { this.enterState(states_exports.BEGIN_DELIMITED_HTML_BLOCK); this.pos--; } else { this.emitError( this.pos, 8 /* INVALID_LINE_START */, 'A line in concise mode cannot start with a single hyphen. Use "--" instead. See: https://github.com/marko-js/htmljs-parser/issues/43' ); } return; case 47 /* FORWARD_SLASH */: switch (this.lookAtCharCodeAhead(1)) { case 47 /* FORWARD_SLASH */: this.enterState(states_exports.JS_COMMENT_LINE); this.pos++; return; case 42 /* ASTERISK */: this.enterState(states_exports.JS_COMMENT_BLOCK); this.pos++; return; default: this.emitError( this.pos, 8 /* INVALID_LINE_START */, 'A line in concise mode cannot start with "/" unless it starts a "//" or "/*" comment' ); return; } } this.enterState(states_exports.OPEN_TAG); this.forward = 0; } }, eol() { this.indent = ""; }, eof: htmlEOF, return(child) { var _a, _b, _c, _d; this.indent = ""; this.isConcise = true; switch (child.state) { case states_exports.JS_COMMENT_LINE: (_b = (_a = this.options).onComment) == null ? void 0 : _b.call(_a, { start: child.start, end: child.end, value: { start: child.start + 2, // strip // end: child.end } }); break; case states_exports.JS_COMMENT_BLOCK: { (_d = (_c = this.options).onComment) == null ? void 0 : _d.call(_c, { start: child.start, end: child.end, value: { start: child.start + 2, // strip /* end: child.end - 2 // strip */, } }); if (!this.consumeWhitespaceOnLine(0)) { this.emitError( this.pos, 4 /* INVALID_CHARACTER */, "In concise mode a javascript comment block can only be followed by whitespace characters and a newline." ); } break; } } } }; // src/states/DECLARATION.ts var DECLARATION = { name: "DECLARATION", enter(parent, start) { this.endText(); return { state: DECLARATION, parent, start, end: start }; }, exit() { }, char(code, declaration) { if (code === 63 /* QUESTION */) { if (this.lookAtCharCodeAhead(1) === 62 /* CLOSE_ANGLE_BRACKET */) { exitDeclaration(this, declaration, 2); } } else if (code === 62 /* CLOSE_ANGLE_BRACKET */) { exitDeclaration(this, declaration, 1); } }, eol() { }, eof(declaration) { this.emitError( declaration, 17 /* MALFORMED_DECLARATION */, "EOF reached while parsing declaration" ); }, return() { } }; function exitDeclaration(parser, declaration, closeOffset) { var _a, _b; parser.pos += closeOffset; parser.exitState(); (_b = (_a = parser.options).onDeclaration) == null ? void 0 : _b.call(_a, { start: declaration.start, end: declaration.end, value: { start: declaration.start + 2, // strip <? end: declaration.end - closeOffset // > or ?> } }); } // src/states/DTD.ts var DTD = { name: "DTD", enter(parent, start) { this.endText(); return { state: DTD, parent, start, end: start }; }, exit(documentType) { var _a, _b; (_b = (_a = this.options).onDoctype) == null ? void 0 : _b.call(_a, { start: documentType.start, end: documentType.end, value: { start: documentType.start + 2, // strip <! end: documentType.end - 1 // strip > } }); }, char(code) { if (code === 62 /* CLOSE_ANGLE_BRACKET */) { this.pos++; this.exitState(); } }, eol() { }, eof(documentType) { this.emitError( documentType, 18 /* MALFORMED_DOCUMENT_TYPE */, "EOF reached while parsing document type" ); }, return() { } }; // src/states/EXPRESSION.ts var shouldTerminate = () => false; var unaryKeywords = [ "async", "await", "class", "function", "new", "typeof", "void" ]; var tsUnaryKeywords = [ ...unaryKeywords, "asserts", "infer", "is", "keyof", "readonly", "unique" ]; var binaryKeywords = [ "as", "extends", "instanceof", // Note: instanceof must be checked before `in` "in", "satisfies" ]; var EXPRESSION = { name: "EXPRESSION", enter(parent, start) { return { state: EXPRESSION, parent, start, end: start, groupStack: [], shouldTerminate, operators: false, wasComment: false, inType: false, forceType: false, ternaryDepth: 0, terminatedByEOL: false, terminatedByWhitespace: false, consumeIndentedContent: false }; }, exit() { }, char(code, expression) { if (!expression.groupStack.length) { if (expression.terminatedByWhitespace && isWhitespaceCode(code)) { if (!checkForOperators(this, expression, false)) { this.exitState(); } return; } if (expression.shouldTerminate(code, this.data, this.pos, expression)) { let wasExpression = false; if (expression.operators) { const prevNonWhitespacePos = lookBehindWhile( isWhitespaceCode, this.data, this.pos - 1 ); if (prevNonWhitespacePos > expression.start) { wasExpression = lookBehindForOperator( expression, this.data, prevNonWhitespacePos ) !== -1; } } if (!wasExpression) { this.exitState(); return; } } } switch (code) { case 34 /* DOUBLE_QUOTE */: this.enterState(states_exports.STRING); break; case 39 /* SINGLE_QUOTE */: this.enterState(states_exports.STRING).quoteCharCode = code; break; case 96 /* BACKTICK */: this.enterState(states_exports.TEMPLATE_STRIN