diff options
| author | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:54:57 +0100 |
|---|---|---|
| committer | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:57:48 +0100 |
| commit | b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch) | |
| tree | 49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/sucrase/dist/esm/parser/plugins/jsx | |
Docs
Diffstat (limited to 'node_modules/sucrase/dist/esm/parser/plugins/jsx')
| -rw-r--r-- | node_modules/sucrase/dist/esm/parser/plugins/jsx/index.js | 367 | ||||
| -rw-r--r-- | node_modules/sucrase/dist/esm/parser/plugins/jsx/xhtml.js | 256 |
2 files changed, 623 insertions, 0 deletions
diff --git a/node_modules/sucrase/dist/esm/parser/plugins/jsx/index.js b/node_modules/sucrase/dist/esm/parser/plugins/jsx/index.js new file mode 100644 index 0000000..83f3983 --- /dev/null +++ b/node_modules/sucrase/dist/esm/parser/plugins/jsx/index.js @@ -0,0 +1,367 @@ +import { + eat, + finishToken, + getTokenFromCode, + IdentifierRole, + JSXRole, + match, + next, + skipSpace, + Token, +} from "../../tokenizer/index"; +import {TokenType as tt} from "../../tokenizer/types"; +import {input, isTypeScriptEnabled, state} from "../../traverser/base"; +import {parseExpression, parseMaybeAssign} from "../../traverser/expression"; +import {expect, unexpected} from "../../traverser/util"; +import {charCodes} from "../../util/charcodes"; +import {IS_IDENTIFIER_CHAR, IS_IDENTIFIER_START} from "../../util/identifier"; +import {tsTryParseJSXTypeArgument} from "../typescript"; + +/** + * Read token with JSX contents. + * + * In addition to detecting jsxTagStart and also regular tokens that might be + * part of an expression, this code detects the start and end of text ranges + * within JSX children. In order to properly count the number of children, we + * distinguish jsxText from jsxEmptyText, which is a text range that simplifies + * to the empty string after JSX whitespace trimming. + * + * It turns out that a JSX text range will simplify to the empty string if and + * only if both of these conditions hold: + * - The range consists entirely of whitespace characters (only counting space, + * tab, \r, and \n). + * - The range has at least one newline. + * This can be proven by analyzing any implementation of whitespace trimming, + * e.g. formatJSXTextLiteral in Sucrase or cleanJSXElementLiteralChild in Babel. + */ +function jsxReadToken() { + let sawNewline = false; + let sawNonWhitespace = false; + while (true) { + if (state.pos >= input.length) { + unexpected("Unterminated JSX contents"); + return; + } + + const ch = input.charCodeAt(state.pos); + if (ch === charCodes.lessThan || ch === charCodes.leftCurlyBrace) { + if (state.pos === state.start) { + if (ch === charCodes.lessThan) { + state.pos++; + finishToken(tt.jsxTagStart); + return; + } + getTokenFromCode(ch); + return; + } + if (sawNewline && !sawNonWhitespace) { + finishToken(tt.jsxEmptyText); + } else { + finishToken(tt.jsxText); + } + return; + } + + // This is part of JSX text. + if (ch === charCodes.lineFeed) { + sawNewline = true; + } else if (ch !== charCodes.space && ch !== charCodes.carriageReturn && ch !== charCodes.tab) { + sawNonWhitespace = true; + } + state.pos++; + } +} + +function jsxReadString(quote) { + state.pos++; + for (;;) { + if (state.pos >= input.length) { + unexpected("Unterminated string constant"); + return; + } + + const ch = input.charCodeAt(state.pos); + if (ch === quote) { + state.pos++; + break; + } + state.pos++; + } + finishToken(tt.string); +} + +// Read a JSX identifier (valid tag or attribute name). +// +// Optimized version since JSX identifiers can't contain +// escape characters and so can be read as single slice. +// Also assumes that first character was already checked +// by isIdentifierStart in readToken. + +function jsxReadWord() { + let ch; + do { + if (state.pos > input.length) { + unexpected("Unexpectedly reached the end of input."); + return; + } + ch = input.charCodeAt(++state.pos); + } while (IS_IDENTIFIER_CHAR[ch] || ch === charCodes.dash); + finishToken(tt.jsxName); +} + +// Parse next token as JSX identifier +function jsxParseIdentifier() { + nextJSXTagToken(); +} + +// Parse namespaced identifier. +function jsxParseNamespacedName(identifierRole) { + jsxParseIdentifier(); + if (!eat(tt.colon)) { + // Plain identifier, so this is an access. + state.tokens[state.tokens.length - 1].identifierRole = identifierRole; + return; + } + // Process the second half of the namespaced name. + jsxParseIdentifier(); +} + +// Parses element name in any form - namespaced, member +// or single identifier. +function jsxParseElementName() { + const firstTokenIndex = state.tokens.length; + jsxParseNamespacedName(IdentifierRole.Access); + let hadDot = false; + while (match(tt.dot)) { + hadDot = true; + nextJSXTagToken(); + jsxParseIdentifier(); + } + // For tags like <div> with a lowercase letter and no dots, the name is + // actually *not* an identifier access, since it's referring to a built-in + // tag name. Remove the identifier role in this case so that it's not + // accidentally transformed by the imports transform when preserving JSX. + if (!hadDot) { + const firstToken = state.tokens[firstTokenIndex]; + const firstChar = input.charCodeAt(firstToken.start); + if (firstChar >= charCodes.lowercaseA && firstChar <= charCodes.lowercaseZ) { + firstToken.identifierRole = null; + } + } +} + +// Parses any type of JSX attribute value. +function jsxParseAttributeValue() { + switch (state.type) { + case tt.braceL: + next(); + parseExpression(); + nextJSXTagToken(); + return; + + case tt.jsxTagStart: + jsxParseElement(); + nextJSXTagToken(); + return; + + case tt.string: + nextJSXTagToken(); + return; + + default: + unexpected("JSX value should be either an expression or a quoted JSX text"); + } +} + +// Parse JSX spread child, after already processing the { +// Does not parse the closing } +function jsxParseSpreadChild() { + expect(tt.ellipsis); + parseExpression(); +} + +// Parses JSX opening tag starting after "<". +// Returns true if the tag was self-closing. +// Does not parse the last token. +function jsxParseOpeningElement(initialTokenIndex) { + if (match(tt.jsxTagEnd)) { + // This is an open-fragment. + return false; + } + jsxParseElementName(); + if (isTypeScriptEnabled) { + tsTryParseJSXTypeArgument(); + } + let hasSeenPropSpread = false; + while (!match(tt.slash) && !match(tt.jsxTagEnd) && !state.error) { + if (eat(tt.braceL)) { + hasSeenPropSpread = true; + expect(tt.ellipsis); + parseMaybeAssign(); + // } + nextJSXTagToken(); + continue; + } + if ( + hasSeenPropSpread && + state.end - state.start === 3 && + input.charCodeAt(state.start) === charCodes.lowercaseK && + input.charCodeAt(state.start + 1) === charCodes.lowercaseE && + input.charCodeAt(state.start + 2) === charCodes.lowercaseY + ) { + state.tokens[initialTokenIndex].jsxRole = JSXRole.KeyAfterPropSpread; + } + jsxParseNamespacedName(IdentifierRole.ObjectKey); + if (match(tt.eq)) { + nextJSXTagToken(); + jsxParseAttributeValue(); + } + } + const isSelfClosing = match(tt.slash); + if (isSelfClosing) { + // / + nextJSXTagToken(); + } + return isSelfClosing; +} + +// Parses JSX closing tag starting after "</". +// Does not parse the last token. +function jsxParseClosingElement() { + if (match(tt.jsxTagEnd)) { + // Fragment syntax, so we immediately have a tag end. + return; + } + jsxParseElementName(); +} + +// Parses entire JSX element, including its opening tag +// (starting after "<"), attributes, contents and closing tag. +// Does not parse the last token. +function jsxParseElementAt() { + const initialTokenIndex = state.tokens.length - 1; + state.tokens[initialTokenIndex].jsxRole = JSXRole.NoChildren; + let numExplicitChildren = 0; + const isSelfClosing = jsxParseOpeningElement(initialTokenIndex); + if (!isSelfClosing) { + nextJSXExprToken(); + while (true) { + switch (state.type) { + case tt.jsxTagStart: + nextJSXTagToken(); + if (match(tt.slash)) { + nextJSXTagToken(); + jsxParseClosingElement(); + // Key after prop spread takes precedence over number of children, + // since it means we switch to createElement, which doesn't care + // about number of children. + if (state.tokens[initialTokenIndex].jsxRole !== JSXRole.KeyAfterPropSpread) { + if (numExplicitChildren === 1) { + state.tokens[initialTokenIndex].jsxRole = JSXRole.OneChild; + } else if (numExplicitChildren > 1) { + state.tokens[initialTokenIndex].jsxRole = JSXRole.StaticChildren; + } + } + return; + } + numExplicitChildren++; + jsxParseElementAt(); + nextJSXExprToken(); + break; + + case tt.jsxText: + numExplicitChildren++; + nextJSXExprToken(); + break; + + case tt.jsxEmptyText: + nextJSXExprToken(); + break; + + case tt.braceL: + next(); + if (match(tt.ellipsis)) { + jsxParseSpreadChild(); + nextJSXExprToken(); + // Spread children are a mechanism to explicitly mark children as + // static, so count it as 2 children to satisfy the "more than one + // child" condition. + numExplicitChildren += 2; + } else { + // If we see {}, this is an empty pseudo-expression that doesn't + // count as a child. + if (!match(tt.braceR)) { + numExplicitChildren++; + parseExpression(); + } + nextJSXExprToken(); + } + + break; + + // istanbul ignore next - should never happen + default: + unexpected(); + return; + } + } + } +} + +// Parses entire JSX element from current position. +// Does not parse the last token. +export function jsxParseElement() { + nextJSXTagToken(); + jsxParseElementAt(); +} + +// ================================== +// Overrides +// ================================== + +export function nextJSXTagToken() { + state.tokens.push(new Token()); + skipSpace(); + state.start = state.pos; + const code = input.charCodeAt(state.pos); + + if (IS_IDENTIFIER_START[code]) { + jsxReadWord(); + } else if (code === charCodes.quotationMark || code === charCodes.apostrophe) { + jsxReadString(code); + } else { + // The following tokens are just one character each. + ++state.pos; + switch (code) { + case charCodes.greaterThan: + finishToken(tt.jsxTagEnd); + break; + case charCodes.lessThan: + finishToken(tt.jsxTagStart); + break; + case charCodes.slash: + finishToken(tt.slash); + break; + case charCodes.equalsTo: + finishToken(tt.eq); + break; + case charCodes.leftCurlyBrace: + finishToken(tt.braceL); + break; + case charCodes.dot: + finishToken(tt.dot); + break; + case charCodes.colon: + finishToken(tt.colon); + break; + default: + unexpected(); + } + } +} + +function nextJSXExprToken() { + state.tokens.push(new Token()); + state.start = state.pos; + jsxReadToken(); +} diff --git a/node_modules/sucrase/dist/esm/parser/plugins/jsx/xhtml.js b/node_modules/sucrase/dist/esm/parser/plugins/jsx/xhtml.js new file mode 100644 index 0000000..c6a0741 --- /dev/null +++ b/node_modules/sucrase/dist/esm/parser/plugins/jsx/xhtml.js @@ -0,0 +1,256 @@ +// Use a Map rather than object to avoid unexpected __proto__ access. +export default new Map([ + ["quot", "\u0022"], + ["amp", "&"], + ["apos", "\u0027"], + ["lt", "<"], + ["gt", ">"], + ["nbsp", "\u00A0"], + ["iexcl", "\u00A1"], + ["cent", "\u00A2"], + ["pound", "\u00A3"], + ["curren", "\u00A4"], + ["yen", "\u00A5"], + ["brvbar", "\u00A6"], + ["sect", "\u00A7"], + ["uml", "\u00A8"], + ["copy", "\u00A9"], + ["ordf", "\u00AA"], + ["laquo", "\u00AB"], + ["not", "\u00AC"], + ["shy", "\u00AD"], + ["reg", "\u00AE"], + ["macr", "\u00AF"], + ["deg", "\u00B0"], + ["plusmn", "\u00B1"], + ["sup2", "\u00B2"], + ["sup3", "\u00B3"], + ["acute", "\u00B4"], + ["micro", "\u00B5"], + ["para", "\u00B6"], + ["middot", "\u00B7"], + ["cedil", "\u00B8"], + ["sup1", "\u00B9"], + ["ordm", "\u00BA"], + ["raquo", "\u00BB"], + ["frac14", "\u00BC"], + ["frac12", "\u00BD"], + ["frac34", "\u00BE"], + ["iquest", "\u00BF"], + ["Agrave", "\u00C0"], + ["Aacute", "\u00C1"], + ["Acirc", "\u00C2"], + ["Atilde", "\u00C3"], + ["Auml", "\u00C4"], + ["Aring", "\u00C5"], + ["AElig", "\u00C6"], + ["Ccedil", "\u00C7"], + ["Egrave", "\u00C8"], + ["Eacute", "\u00C9"], + ["Ecirc", "\u00CA"], + ["Euml", "\u00CB"], + ["Igrave", "\u00CC"], + ["Iacute", "\u00CD"], + ["Icirc", "\u00CE"], + ["Iuml", "\u00CF"], + ["ETH", "\u00D0"], + ["Ntilde", "\u00D1"], + ["Ograve", "\u00D2"], + ["Oacute", "\u00D3"], + ["Ocirc", "\u00D4"], + ["Otilde", "\u00D5"], + ["Ouml", "\u00D6"], + ["times", "\u00D7"], + ["Oslash", "\u00D8"], + ["Ugrave", "\u00D9"], + ["Uacute", "\u00DA"], + ["Ucirc", "\u00DB"], + ["Uuml", "\u00DC"], + ["Yacute", "\u00DD"], + ["THORN", "\u00DE"], + ["szlig", "\u00DF"], + ["agrave", "\u00E0"], + ["aacute", "\u00E1"], + ["acirc", "\u00E2"], + ["atilde", "\u00E3"], + ["auml", "\u00E4"], + ["aring", "\u00E5"], + ["aelig", "\u00E6"], + ["ccedil", "\u00E7"], + ["egrave", "\u00E8"], + ["eacute", "\u00E9"], + ["ecirc", "\u00EA"], + ["euml", "\u00EB"], + ["igrave", "\u00EC"], + ["iacute", "\u00ED"], + ["icirc", "\u00EE"], + ["iuml", "\u00EF"], + ["eth", "\u00F0"], + ["ntilde", "\u00F1"], + ["ograve", "\u00F2"], + ["oacute", "\u00F3"], + ["ocirc", "\u00F4"], + ["otilde", "\u00F5"], + ["ouml", "\u00F6"], + ["divide", "\u00F7"], + ["oslash", "\u00F8"], + ["ugrave", "\u00F9"], + ["uacute", "\u00FA"], + ["ucirc", "\u00FB"], + ["uuml", "\u00FC"], + ["yacute", "\u00FD"], + ["thorn", "\u00FE"], + ["yuml", "\u00FF"], + ["OElig", "\u0152"], + ["oelig", "\u0153"], + ["Scaron", "\u0160"], + ["scaron", "\u0161"], + ["Yuml", "\u0178"], + ["fnof", "\u0192"], + ["circ", "\u02C6"], + ["tilde", "\u02DC"], + ["Alpha", "\u0391"], + ["Beta", "\u0392"], + ["Gamma", "\u0393"], + ["Delta", "\u0394"], + ["Epsilon", "\u0395"], + ["Zeta", "\u0396"], + ["Eta", "\u0397"], + ["Theta", "\u0398"], + ["Iota", "\u0399"], + ["Kappa", "\u039A"], + ["Lambda", "\u039B"], + ["Mu", "\u039C"], + ["Nu", "\u039D"], + ["Xi", "\u039E"], + ["Omicron", "\u039F"], + ["Pi", "\u03A0"], + ["Rho", "\u03A1"], + ["Sigma", "\u03A3"], + ["Tau", "\u03A4"], + ["Upsilon", "\u03A5"], + ["Phi", "\u03A6"], + ["Chi", "\u03A7"], + ["Psi", "\u03A8"], + ["Omega", "\u03A9"], + ["alpha", "\u03B1"], + ["beta", "\u03B2"], + ["gamma", "\u03B3"], + ["delta", "\u03B4"], + ["epsilon", "\u03B5"], + ["zeta", "\u03B6"], + ["eta", "\u03B7"], + ["theta", "\u03B8"], + ["iota", "\u03B9"], + ["kappa", "\u03BA"], + ["lambda", "\u03BB"], + ["mu", "\u03BC"], + ["nu", "\u03BD"], + ["xi", "\u03BE"], + ["omicron", "\u03BF"], + ["pi", "\u03C0"], + ["rho", "\u03C1"], + ["sigmaf", "\u03C2"], + ["sigma", "\u03C3"], + ["tau", "\u03C4"], + ["upsilon", "\u03C5"], + ["phi", "\u03C6"], + ["chi", "\u03C7"], + ["psi", "\u03C8"], + ["omega", "\u03C9"], + ["thetasym", "\u03D1"], + ["upsih", "\u03D2"], + ["piv", "\u03D6"], + ["ensp", "\u2002"], + ["emsp", "\u2003"], + ["thinsp", "\u2009"], + ["zwnj", "\u200C"], + ["zwj", "\u200D"], + ["lrm", "\u200E"], + ["rlm", "\u200F"], + ["ndash", "\u2013"], + ["mdash", "\u2014"], + ["lsquo", "\u2018"], + ["rsquo", "\u2019"], + ["sbquo", "\u201A"], + ["ldquo", "\u201C"], + ["rdquo", "\u201D"], + ["bdquo", "\u201E"], + ["dagger", "\u2020"], + ["Dagger", "\u2021"], + ["bull", "\u2022"], + ["hellip", "\u2026"], + ["permil", "\u2030"], + ["prime", "\u2032"], + ["Prime", "\u2033"], + ["lsaquo", "\u2039"], + ["rsaquo", "\u203A"], + ["oline", "\u203E"], + ["frasl", "\u2044"], + ["euro", "\u20AC"], + ["image", "\u2111"], + ["weierp", "\u2118"], + ["real", "\u211C"], + ["trade", "\u2122"], + ["alefsym", "\u2135"], + ["larr", "\u2190"], + ["uarr", "\u2191"], + ["rarr", "\u2192"], + ["darr", "\u2193"], + ["harr", "\u2194"], + ["crarr", "\u21B5"], + ["lArr", "\u21D0"], + ["uArr", "\u21D1"], + ["rArr", "\u21D2"], + ["dArr", "\u21D3"], + ["hArr", "\u21D4"], + ["forall", "\u2200"], + ["part", "\u2202"], + ["exist", "\u2203"], + ["empty", "\u2205"], + ["nabla", "\u2207"], + ["isin", "\u2208"], + ["notin", "\u2209"], + ["ni", "\u220B"], + ["prod", "\u220F"], + ["sum", "\u2211"], + ["minus", "\u2212"], + ["lowast", "\u2217"], + ["radic", "\u221A"], + ["prop", "\u221D"], + ["infin", "\u221E"], + ["ang", "\u2220"], + ["and", "\u2227"], + ["or", "\u2228"], + ["cap", "\u2229"], + ["cup", "\u222A"], + ["int", "\u222B"], + ["there4", "\u2234"], + ["sim", "\u223C"], + ["cong", "\u2245"], + ["asymp", "\u2248"], + ["ne", "\u2260"], + ["equiv", "\u2261"], + ["le", "\u2264"], + ["ge", "\u2265"], + ["sub", "\u2282"], + ["sup", "\u2283"], + ["nsub", "\u2284"], + ["sube", "\u2286"], + ["supe", "\u2287"], + ["oplus", "\u2295"], + ["otimes", "\u2297"], + ["perp", "\u22A5"], + ["sdot", "\u22C5"], + ["lceil", "\u2308"], + ["rceil", "\u2309"], + ["lfloor", "\u230A"], + ["rfloor", "\u230B"], + ["lang", "\u2329"], + ["rang", "\u232A"], + ["loz", "\u25CA"], + ["spades", "\u2660"], + ["clubs", "\u2663"], + ["hearts", "\u2665"], + ["diams", "\u2666"], +]); |