Comparing version 3.25.0 to 3.26.0
@@ -172,3 +172,3 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
getFreeIdentifierForPath(path) { | ||
getFreeIdentifierForPath(path) { | ||
const components = path.split("/"); | ||
@@ -175,0 +175,0 @@ const lastComponent = components[components.length - 1]; |
@@ -172,3 +172,3 @@ | ||
getFreeIdentifierForPath(path) { | ||
getFreeIdentifierForPath(path) { | ||
const components = path.split("/"); | ||
@@ -175,0 +175,0 @@ const lastComponent = components[components.length - 1]; |
@@ -33,3 +33,3 @@ import CJSImportProcessor from "./CJSImportProcessor"; | ||
export function getVersion() { | ||
return "3.25.0"; | ||
return "3.26.0"; | ||
} | ||
@@ -36,0 +36,0 @@ |
@@ -22,4 +22,10 @@ /** | ||
transforms: t.array("Transform"), | ||
disableESTransforms: t.opt("boolean"), | ||
jsxRuntime: t.opt(t.union(t.lit("classic"), t.lit("automatic"))), | ||
production: t.opt("boolean"), | ||
jsxImportSource: t.opt("string"), | ||
jsxPragma: t.opt("string"), | ||
jsxFragmentPragma: t.opt("string"), | ||
preserveDynamicImport: t.opt("boolean"), | ||
injectCreateRequireForImportRequire: t.opt("boolean"), | ||
enableLegacyTypeScriptModuleInterop: t.opt("boolean"), | ||
@@ -29,6 +35,2 @@ enableLegacyBabel5ModuleInterop: t.opt("boolean"), | ||
filePath: t.opt("string"), | ||
production: t.opt("boolean"), | ||
disableESTransforms: t.opt("boolean"), | ||
preserveDynamicImport: t.opt("boolean"), | ||
injectCreateRequireForImportRequire: t.opt("boolean"), | ||
}); | ||
@@ -35,0 +37,0 @@ |
@@ -72,4 +72,26 @@ import {createCheckers} from "ts-interface-checker"; | ||
export function validateOptions(options) { | ||
OptionsChecker.strictCheck(options); | ||
} |
@@ -6,2 +6,3 @@ import { | ||
IdentifierRole, | ||
JSXRole, | ||
match, | ||
@@ -20,5 +21,23 @@ next, | ||
// Reads inline JSX contents token. | ||
/** | ||
* 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() { | ||
for (;;) { | ||
let sawNewline = false; | ||
let sawNonWhitespace = false; | ||
while (true) { | ||
if (state.pos >= input.length) { | ||
@@ -30,21 +49,27 @@ unexpected("Unterminated JSX contents"); | ||
const ch = input.charCodeAt(state.pos); | ||
switch (ch) { | ||
case charCodes.lessThan: | ||
case charCodes.leftCurlyBrace: | ||
if (state.pos === state.start) { | ||
if (ch === charCodes.lessThan) { | ||
state.pos++; | ||
finishToken(tt.jsxTagStart); | ||
return; | ||
} | ||
getTokenFromCode(ch); | ||
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; | ||
} | ||
return; | ||
} | ||
default: | ||
state.pos++; | ||
// 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++; | ||
} | ||
@@ -122,3 +147,3 @@ } | ||
next(); | ||
jsxParseExpressionContainer(); | ||
parseExpression(); | ||
nextJSXTagToken(); | ||
@@ -141,6 +166,2 @@ return; | ||
function jsxParseEmptyExpression() { | ||
// Do nothing. | ||
} | ||
// Parse JSX spread child, after already processing the { | ||
@@ -153,32 +174,6 @@ // Does not parse the closing } | ||
// Parses JSX expression enclosed into curly brackets, after already processing the { | ||
// Does not parse the closing } | ||
function jsxParseExpressionContainer() { | ||
if (match(tt.braceR)) { | ||
jsxParseEmptyExpression(); | ||
} else { | ||
parseExpression(); | ||
} | ||
} | ||
// Parses following JSX attribute name-value pair. | ||
function jsxParseAttribute() { | ||
if (eat(tt.braceL)) { | ||
expect(tt.ellipsis); | ||
parseMaybeAssign(); | ||
// } | ||
nextJSXTagToken(); | ||
return; | ||
} | ||
jsxParseNamespacedName(IdentifierRole.ObjectKey); | ||
if (match(tt.eq)) { | ||
nextJSXTagToken(); | ||
jsxParseAttributeValue(); | ||
} | ||
} | ||
// Parses JSX opening tag starting after "<". | ||
// Returns true if the tag was self-closing. | ||
// Does not parse the last token. | ||
function jsxParseOpeningElement() { | ||
function jsxParseOpeningElement(initialTokenIndex) { | ||
if (match(tt.jsxTagEnd)) { | ||
@@ -192,4 +187,26 @@ // This is an open-fragment. | ||
} | ||
let hasSeenPropSpread = false; | ||
while (!match(tt.slash) && !match(tt.jsxTagEnd) && !state.error) { | ||
jsxParseAttribute(); | ||
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(); | ||
} | ||
} | ||
@@ -218,3 +235,6 @@ const isSelfClosing = match(tt.slash); | ||
function jsxParseElementAt() { | ||
const isSelfClosing = jsxParseOpeningElement(); | ||
const initialTokenIndex = state.tokens.length - 1; | ||
state.tokens[initialTokenIndex].jsxRole = JSXRole.NoChildren; | ||
let numExplicitChildren = 0; | ||
const isSelfClosing = jsxParseOpeningElement(initialTokenIndex); | ||
if (!isSelfClosing) { | ||
@@ -229,4 +249,15 @@ nextJSXExprToken(); | ||
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(); | ||
@@ -237,5 +268,10 @@ nextJSXExprToken(); | ||
case tt.jsxText: | ||
numExplicitChildren++; | ||
nextJSXExprToken(); | ||
break; | ||
case tt.jsxEmptyText: | ||
nextJSXExprToken(); | ||
break; | ||
case tt.braceL: | ||
@@ -246,4 +282,13 @@ next(); | ||
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 { | ||
jsxParseExpressionContainer(); | ||
// If we see {}, this is an empty pseudo-expression that doesn't | ||
// count as a child. | ||
if (!match(tt.braceR)) { | ||
numExplicitChildren++; | ||
parseExpression(); | ||
} | ||
nextJSXExprToken(); | ||
@@ -250,0 +295,0 @@ } |
@@ -30,2 +30,22 @@ /* eslint max-len: 0 */ | ||
/** | ||
* Extra information on jsxTagStart tokens, used to determine which of the three | ||
* jsx functions are called in the automatic transform. | ||
*/ | ||
export var JSXRole; (function (JSXRole) { | ||
// The element is self-closing or has a body that resolves to empty. We | ||
// shouldn't emit children at all in this case. | ||
const NoChildren = 0; JSXRole[JSXRole["NoChildren"] = NoChildren] = "NoChildren"; | ||
// The element has a single explicit child, which might still be an arbitrary | ||
// expression like an array. We should emit that expression as the children. | ||
const OneChild = NoChildren + 1; JSXRole[JSXRole["OneChild"] = OneChild] = "OneChild"; | ||
// The element has at least two explicitly-specified children or has spread | ||
// children, so child positions are assumed to be "static". We should wrap | ||
// these children in an array. | ||
const StaticChildren = OneChild + 1; JSXRole[JSXRole["StaticChildren"] = StaticChildren] = "StaticChildren"; | ||
// The element has a prop named "key" after a prop spread, so we should fall | ||
// back to the createElement function. | ||
const KeyAfterPropSpread = StaticChildren + 1; JSXRole[JSXRole["KeyAfterPropSpread"] = KeyAfterPropSpread] = "KeyAfterPropSpread"; | ||
})(JSXRole || (JSXRole = {})); | ||
export function isDeclaration(token) { | ||
@@ -101,2 +121,3 @@ const role = token.identifierRole; | ||
this.identifierRole = null; | ||
this.jsxRole = null; | ||
this.shadowsGlobal = false; | ||
@@ -122,2 +143,3 @@ this.isAsyncOperation = false; | ||
// Initially false for all tokens, then may be computed in a follow-up step that does scope | ||
@@ -124,0 +146,0 @@ // analysis. |
@@ -72,58 +72,59 @@ // Generated file, do not edit! Run "yarn generate" to re-generate this file. | ||
const jsxText = 56320; TokenType[TokenType["jsxText"] = jsxText] = "jsxText"; // jsxText | ||
const jsxTagStart = 57856; TokenType[TokenType["jsxTagStart"] = jsxTagStart] = "jsxTagStart"; // jsxTagStart startsExpr | ||
const jsxTagEnd = 58368; TokenType[TokenType["jsxTagEnd"] = jsxTagEnd] = "jsxTagEnd"; // jsxTagEnd | ||
const typeParameterStart = 59904; TokenType[TokenType["typeParameterStart"] = typeParameterStart] = "typeParameterStart"; // typeParameterStart startsExpr | ||
const nonNullAssertion = 60416; TokenType[TokenType["nonNullAssertion"] = nonNullAssertion] = "nonNullAssertion"; // nonNullAssertion | ||
const _break = 61456; TokenType[TokenType["_break"] = _break] = "_break"; // break keyword | ||
const _case = 62480; TokenType[TokenType["_case"] = _case] = "_case"; // case keyword | ||
const _catch = 63504; TokenType[TokenType["_catch"] = _catch] = "_catch"; // catch keyword | ||
const _continue = 64528; TokenType[TokenType["_continue"] = _continue] = "_continue"; // continue keyword | ||
const _debugger = 65552; TokenType[TokenType["_debugger"] = _debugger] = "_debugger"; // debugger keyword | ||
const _default = 66576; TokenType[TokenType["_default"] = _default] = "_default"; // default keyword | ||
const _do = 67600; TokenType[TokenType["_do"] = _do] = "_do"; // do keyword | ||
const _else = 68624; TokenType[TokenType["_else"] = _else] = "_else"; // else keyword | ||
const _finally = 69648; TokenType[TokenType["_finally"] = _finally] = "_finally"; // finally keyword | ||
const _for = 70672; TokenType[TokenType["_for"] = _for] = "_for"; // for keyword | ||
const _function = 72208; TokenType[TokenType["_function"] = _function] = "_function"; // function keyword startsExpr | ||
const _if = 72720; TokenType[TokenType["_if"] = _if] = "_if"; // if keyword | ||
const _return = 73744; TokenType[TokenType["_return"] = _return] = "_return"; // return keyword | ||
const _switch = 74768; TokenType[TokenType["_switch"] = _switch] = "_switch"; // switch keyword | ||
const _throw = 76432; TokenType[TokenType["_throw"] = _throw] = "_throw"; // throw keyword prefix startsExpr | ||
const _try = 76816; TokenType[TokenType["_try"] = _try] = "_try"; // try keyword | ||
const _var = 77840; TokenType[TokenType["_var"] = _var] = "_var"; // var keyword | ||
const _let = 78864; TokenType[TokenType["_let"] = _let] = "_let"; // let keyword | ||
const _const = 79888; TokenType[TokenType["_const"] = _const] = "_const"; // const keyword | ||
const _while = 80912; TokenType[TokenType["_while"] = _while] = "_while"; // while keyword | ||
const _with = 81936; TokenType[TokenType["_with"] = _with] = "_with"; // with keyword | ||
const _new = 83472; TokenType[TokenType["_new"] = _new] = "_new"; // new keyword startsExpr | ||
const _this = 84496; TokenType[TokenType["_this"] = _this] = "_this"; // this keyword startsExpr | ||
const _super = 85520; TokenType[TokenType["_super"] = _super] = "_super"; // super keyword startsExpr | ||
const _class = 86544; TokenType[TokenType["_class"] = _class] = "_class"; // class keyword startsExpr | ||
const _extends = 87056; TokenType[TokenType["_extends"] = _extends] = "_extends"; // extends keyword | ||
const _export = 88080; TokenType[TokenType["_export"] = _export] = "_export"; // export keyword | ||
const _import = 89616; TokenType[TokenType["_import"] = _import] = "_import"; // import keyword startsExpr | ||
const _yield = 90640; TokenType[TokenType["_yield"] = _yield] = "_yield"; // yield keyword startsExpr | ||
const _null = 91664; TokenType[TokenType["_null"] = _null] = "_null"; // null keyword startsExpr | ||
const _true = 92688; TokenType[TokenType["_true"] = _true] = "_true"; // true keyword startsExpr | ||
const _false = 93712; TokenType[TokenType["_false"] = _false] = "_false"; // false keyword startsExpr | ||
const _in = 94232; TokenType[TokenType["_in"] = _in] = "_in"; // in prec:8 keyword | ||
const _instanceof = 95256; TokenType[TokenType["_instanceof"] = _instanceof] = "_instanceof"; // instanceof prec:8 keyword | ||
const _typeof = 96912; TokenType[TokenType["_typeof"] = _typeof] = "_typeof"; // typeof keyword prefix startsExpr | ||
const _void = 97936; TokenType[TokenType["_void"] = _void] = "_void"; // void keyword prefix startsExpr | ||
const _delete = 98960; TokenType[TokenType["_delete"] = _delete] = "_delete"; // delete keyword prefix startsExpr | ||
const _async = 99856; TokenType[TokenType["_async"] = _async] = "_async"; // async keyword startsExpr | ||
const _get = 100880; TokenType[TokenType["_get"] = _get] = "_get"; // get keyword startsExpr | ||
const _set = 101904; TokenType[TokenType["_set"] = _set] = "_set"; // set keyword startsExpr | ||
const _declare = 102928; TokenType[TokenType["_declare"] = _declare] = "_declare"; // declare keyword startsExpr | ||
const _readonly = 103952; TokenType[TokenType["_readonly"] = _readonly] = "_readonly"; // readonly keyword startsExpr | ||
const _abstract = 104976; TokenType[TokenType["_abstract"] = _abstract] = "_abstract"; // abstract keyword startsExpr | ||
const _static = 106000; TokenType[TokenType["_static"] = _static] = "_static"; // static keyword startsExpr | ||
const _public = 106512; TokenType[TokenType["_public"] = _public] = "_public"; // public keyword | ||
const _private = 107536; TokenType[TokenType["_private"] = _private] = "_private"; // private keyword | ||
const _protected = 108560; TokenType[TokenType["_protected"] = _protected] = "_protected"; // protected keyword | ||
const _override = 109584; TokenType[TokenType["_override"] = _override] = "_override"; // override keyword | ||
const _as = 111120; TokenType[TokenType["_as"] = _as] = "_as"; // as keyword startsExpr | ||
const _enum = 112144; TokenType[TokenType["_enum"] = _enum] = "_enum"; // enum keyword startsExpr | ||
const _type = 113168; TokenType[TokenType["_type"] = _type] = "_type"; // type keyword startsExpr | ||
const _implements = 114192; TokenType[TokenType["_implements"] = _implements] = "_implements"; // implements keyword startsExpr | ||
const jsxEmptyText = 57344; TokenType[TokenType["jsxEmptyText"] = jsxEmptyText] = "jsxEmptyText"; // jsxEmptyText | ||
const jsxTagStart = 58880; TokenType[TokenType["jsxTagStart"] = jsxTagStart] = "jsxTagStart"; // jsxTagStart startsExpr | ||
const jsxTagEnd = 59392; TokenType[TokenType["jsxTagEnd"] = jsxTagEnd] = "jsxTagEnd"; // jsxTagEnd | ||
const typeParameterStart = 60928; TokenType[TokenType["typeParameterStart"] = typeParameterStart] = "typeParameterStart"; // typeParameterStart startsExpr | ||
const nonNullAssertion = 61440; TokenType[TokenType["nonNullAssertion"] = nonNullAssertion] = "nonNullAssertion"; // nonNullAssertion | ||
const _break = 62480; TokenType[TokenType["_break"] = _break] = "_break"; // break keyword | ||
const _case = 63504; TokenType[TokenType["_case"] = _case] = "_case"; // case keyword | ||
const _catch = 64528; TokenType[TokenType["_catch"] = _catch] = "_catch"; // catch keyword | ||
const _continue = 65552; TokenType[TokenType["_continue"] = _continue] = "_continue"; // continue keyword | ||
const _debugger = 66576; TokenType[TokenType["_debugger"] = _debugger] = "_debugger"; // debugger keyword | ||
const _default = 67600; TokenType[TokenType["_default"] = _default] = "_default"; // default keyword | ||
const _do = 68624; TokenType[TokenType["_do"] = _do] = "_do"; // do keyword | ||
const _else = 69648; TokenType[TokenType["_else"] = _else] = "_else"; // else keyword | ||
const _finally = 70672; TokenType[TokenType["_finally"] = _finally] = "_finally"; // finally keyword | ||
const _for = 71696; TokenType[TokenType["_for"] = _for] = "_for"; // for keyword | ||
const _function = 73232; TokenType[TokenType["_function"] = _function] = "_function"; // function keyword startsExpr | ||
const _if = 73744; TokenType[TokenType["_if"] = _if] = "_if"; // if keyword | ||
const _return = 74768; TokenType[TokenType["_return"] = _return] = "_return"; // return keyword | ||
const _switch = 75792; TokenType[TokenType["_switch"] = _switch] = "_switch"; // switch keyword | ||
const _throw = 77456; TokenType[TokenType["_throw"] = _throw] = "_throw"; // throw keyword prefix startsExpr | ||
const _try = 77840; TokenType[TokenType["_try"] = _try] = "_try"; // try keyword | ||
const _var = 78864; TokenType[TokenType["_var"] = _var] = "_var"; // var keyword | ||
const _let = 79888; TokenType[TokenType["_let"] = _let] = "_let"; // let keyword | ||
const _const = 80912; TokenType[TokenType["_const"] = _const] = "_const"; // const keyword | ||
const _while = 81936; TokenType[TokenType["_while"] = _while] = "_while"; // while keyword | ||
const _with = 82960; TokenType[TokenType["_with"] = _with] = "_with"; // with keyword | ||
const _new = 84496; TokenType[TokenType["_new"] = _new] = "_new"; // new keyword startsExpr | ||
const _this = 85520; TokenType[TokenType["_this"] = _this] = "_this"; // this keyword startsExpr | ||
const _super = 86544; TokenType[TokenType["_super"] = _super] = "_super"; // super keyword startsExpr | ||
const _class = 87568; TokenType[TokenType["_class"] = _class] = "_class"; // class keyword startsExpr | ||
const _extends = 88080; TokenType[TokenType["_extends"] = _extends] = "_extends"; // extends keyword | ||
const _export = 89104; TokenType[TokenType["_export"] = _export] = "_export"; // export keyword | ||
const _import = 90640; TokenType[TokenType["_import"] = _import] = "_import"; // import keyword startsExpr | ||
const _yield = 91664; TokenType[TokenType["_yield"] = _yield] = "_yield"; // yield keyword startsExpr | ||
const _null = 92688; TokenType[TokenType["_null"] = _null] = "_null"; // null keyword startsExpr | ||
const _true = 93712; TokenType[TokenType["_true"] = _true] = "_true"; // true keyword startsExpr | ||
const _false = 94736; TokenType[TokenType["_false"] = _false] = "_false"; // false keyword startsExpr | ||
const _in = 95256; TokenType[TokenType["_in"] = _in] = "_in"; // in prec:8 keyword | ||
const _instanceof = 96280; TokenType[TokenType["_instanceof"] = _instanceof] = "_instanceof"; // instanceof prec:8 keyword | ||
const _typeof = 97936; TokenType[TokenType["_typeof"] = _typeof] = "_typeof"; // typeof keyword prefix startsExpr | ||
const _void = 98960; TokenType[TokenType["_void"] = _void] = "_void"; // void keyword prefix startsExpr | ||
const _delete = 99984; TokenType[TokenType["_delete"] = _delete] = "_delete"; // delete keyword prefix startsExpr | ||
const _async = 100880; TokenType[TokenType["_async"] = _async] = "_async"; // async keyword startsExpr | ||
const _get = 101904; TokenType[TokenType["_get"] = _get] = "_get"; // get keyword startsExpr | ||
const _set = 102928; TokenType[TokenType["_set"] = _set] = "_set"; // set keyword startsExpr | ||
const _declare = 103952; TokenType[TokenType["_declare"] = _declare] = "_declare"; // declare keyword startsExpr | ||
const _readonly = 104976; TokenType[TokenType["_readonly"] = _readonly] = "_readonly"; // readonly keyword startsExpr | ||
const _abstract = 106000; TokenType[TokenType["_abstract"] = _abstract] = "_abstract"; // abstract keyword startsExpr | ||
const _static = 107024; TokenType[TokenType["_static"] = _static] = "_static"; // static keyword startsExpr | ||
const _public = 107536; TokenType[TokenType["_public"] = _public] = "_public"; // public keyword | ||
const _private = 108560; TokenType[TokenType["_private"] = _private] = "_private"; // private keyword | ||
const _protected = 109584; TokenType[TokenType["_protected"] = _protected] = "_protected"; // protected keyword | ||
const _override = 110608; TokenType[TokenType["_override"] = _override] = "_override"; // override keyword | ||
const _as = 112144; TokenType[TokenType["_as"] = _as] = "_as"; // as keyword startsExpr | ||
const _enum = 113168; TokenType[TokenType["_enum"] = _enum] = "_enum"; // enum keyword startsExpr | ||
const _type = 114192; TokenType[TokenType["_type"] = _type] = "_type"; // type keyword startsExpr | ||
const _implements = 115216; TokenType[TokenType["_implements"] = _implements] = "_implements"; // implements keyword startsExpr | ||
})(TokenType || (TokenType = {})); | ||
@@ -244,2 +245,4 @@ export function formatTokenType(tokenType) { | ||
return "jsxText"; | ||
case TokenType.jsxEmptyText: | ||
return "jsxEmptyText"; | ||
case TokenType.jsxTagStart: | ||
@@ -246,0 +249,0 @@ return "jsxTagStart"; |
@@ -451,3 +451,3 @@ /* eslint max-len: 0 */ | ||
if (match(tt.jsxText)) { | ||
if (match(tt.jsxText) || match(tt.jsxEmptyText)) { | ||
parseLiteral(); | ||
@@ -454,0 +454,0 @@ return false; |
export var charCodes; (function (charCodes) { | ||
const backSpace = 8; charCodes[charCodes["backSpace"] = backSpace] = "backSpace"; | ||
const lineFeed = 10; charCodes[charCodes["lineFeed"] = lineFeed] = "lineFeed"; // '\n' | ||
const tab = 9; charCodes[charCodes["tab"] = tab] = "tab"; // '\t' | ||
const carriageReturn = 13; charCodes[charCodes["carriageReturn"] = carriageReturn] = "carriageReturn"; // '\r' | ||
@@ -5,0 +6,0 @@ const shiftOut = 14; charCodes[charCodes["shiftOut"] = shiftOut] = "shiftOut"; |
@@ -36,4 +36,13 @@ | ||
getResultCodeIndex() { | ||
return this.resultCode.length; | ||
/** | ||
* Remove and return the code generated since the snapshot, leaving the | ||
* current token position in-place. Unlike most TokenProcessor operations, | ||
* this operation can result in input/output line number mismatches because | ||
* the removed code may contain newlines, so this operation should be used | ||
* sparingly. | ||
*/ | ||
dangerouslyGetAndRemoveCodeSinceSnapshot(snapshot) { | ||
const result = this.resultCode.slice(snapshot.resultCode.length); | ||
this.resultCode = snapshot.resultCode; | ||
return result; | ||
} | ||
@@ -40,0 +49,0 @@ |
@@ -5,2 +5,3 @@ | ||
import XHTMLEntities from "../parser/plugins/jsx/xhtml"; | ||
import {JSXRole} from "../parser/tokenizer"; | ||
import {TokenType as tt} from "../parser/tokenizer/types"; | ||
@@ -14,6 +15,18 @@ import {charCodes} from "../parser/util/charcodes"; | ||
export default class JSXTransformer extends Transformer { | ||
// State for calculating the line number of each JSX tag in development. | ||
__init() {this.lastLineNumber = 1} | ||
__init2() {this.lastIndex = 0} | ||
// In development, variable name holding the name of the current file. | ||
__init3() {this.filenameVarName = null} | ||
// Mapping of claimed names for imports in the automatic transform, e,g. | ||
// {jsx: "_jsx"}. This determines which imports to generate in the prefix. | ||
__init4() {this.esmAutomaticImportNameResolutions = {}} | ||
// When automatically adding imports in CJS mode, we store the variable name | ||
// holding the imported CJS module so we can require it in the prefix. | ||
__init5() {this.cjsAutomaticModuleNameResolutions = {}} | ||
@@ -27,4 +40,6 @@ constructor( | ||
) { | ||
super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.importProcessor = importProcessor;this.nameManager = nameManager;this.options = options;JSXTransformer.prototype.__init.call(this);JSXTransformer.prototype.__init2.call(this);JSXTransformer.prototype.__init3.call(this);; | ||
super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.importProcessor = importProcessor;this.nameManager = nameManager;this.options = options;JSXTransformer.prototype.__init.call(this);JSXTransformer.prototype.__init2.call(this);JSXTransformer.prototype.__init3.call(this);JSXTransformer.prototype.__init4.call(this);JSXTransformer.prototype.__init5.call(this);; | ||
this.jsxPragmaInfo = getJSXPragmaInfo(options); | ||
this.isAutomaticRuntime = options.jsxRuntime === "automatic"; | ||
this.jsxImportSource = options.jsxImportSource || "react"; | ||
} | ||
@@ -41,12 +56,52 @@ | ||
getPrefixCode() { | ||
let prefix = ""; | ||
if (this.filenameVarName) { | ||
return `const ${this.filenameVarName} = ${JSON.stringify(this.options.filePath || "")};`; | ||
prefix += `const ${this.filenameVarName} = ${JSON.stringify(this.options.filePath || "")};`; | ||
} | ||
if (this.isAutomaticRuntime) { | ||
if (this.importProcessor) { | ||
// CJS mode: emit require statements for all modules that were referenced. | ||
for (const [path, resolvedName] of Object.entries(this.cjsAutomaticModuleNameResolutions)) { | ||
prefix += `var ${resolvedName} = require("${path}");`; | ||
} | ||
} else { | ||
// ESM mode: consolidate and emit import statements for referenced names. | ||
const {createElement: createElementResolution, ...otherResolutions} = | ||
this.esmAutomaticImportNameResolutions; | ||
if (createElementResolution) { | ||
prefix += `import {createElement as ${createElementResolution}} from "${this.jsxImportSource}";`; | ||
} | ||
const importSpecifiers = Object.entries(otherResolutions) | ||
.map(([name, resolvedName]) => `${name} as ${resolvedName}`) | ||
.join(", "); | ||
if (importSpecifiers) { | ||
const importPath = | ||
this.jsxImportSource + (this.options.production ? "/jsx-runtime" : "/jsx-dev-runtime"); | ||
prefix += `import {${importSpecifiers}} from "${importPath}";`; | ||
} | ||
} | ||
} | ||
return prefix; | ||
} | ||
processJSXTag() { | ||
const {jsxRole, start} = this.tokens.currentToken(); | ||
// Calculate line number information at the very start (if in development | ||
// mode) so that the information is guaranteed to be queried in token order. | ||
const elementLocationCode = this.options.production ? null : this.getElementLocationCode(start); | ||
if (this.isAutomaticRuntime && jsxRole !== JSXRole.KeyAfterPropSpread) { | ||
this.transformTagToJSXFunc(elementLocationCode, jsxRole); | ||
} else { | ||
return ""; | ||
this.transformTagToCreateElement(elementLocationCode); | ||
} | ||
} | ||
getElementLocationCode(firstTokenStart) { | ||
const lineNumber = this.getLineNumberForIndex(firstTokenStart); | ||
return `lineNumber: ${lineNumber}`; | ||
} | ||
/** | ||
* Lazily calculate line numbers to avoid unneeded work. We assume this is always called in | ||
* increasing order by index. | ||
* Get the line number for this source position. This is calculated lazily and | ||
* must be called in increasing order by index. | ||
*/ | ||
@@ -64,72 +119,206 @@ getLineNumberForIndex(index) { | ||
getFilenameVarName() { | ||
if (!this.filenameVarName) { | ||
this.filenameVarName = this.nameManager.claimFreeName("_jsxFileName"); | ||
/** | ||
* Convert the current JSX element to a call to jsx, jsxs, or jsxDEV. This is | ||
* the primary transformation for the automatic transform. | ||
* | ||
* Example: | ||
* <div a={1} key={2}>Hello{x}</div> | ||
* becomes | ||
* jsxs('div', {a: 1, children: ["Hello", x]}, 2) | ||
*/ | ||
transformTagToJSXFunc(elementLocationCode, jsxRole) { | ||
const isStatic = jsxRole === JSXRole.StaticChildren; | ||
// First tag is always jsxTagStart. | ||
this.tokens.replaceToken(this.getJSXFuncInvocationCode(isStatic)); | ||
let keyCode = null; | ||
if (this.tokens.matches1(tt.jsxTagEnd)) { | ||
// Fragment syntax. | ||
this.tokens.replaceToken(`${this.getFragmentCode()}, {`); | ||
this.processAutomaticChildrenAndEndProps(jsxRole); | ||
} else { | ||
// Normal open tag or self-closing tag. | ||
this.processTagIntro(); | ||
this.tokens.appendCode(", {"); | ||
keyCode = this.processProps(true); | ||
if (this.tokens.matches2(tt.slash, tt.jsxTagEnd)) { | ||
// Self-closing tag, no children to add, so close the props. | ||
this.tokens.appendCode("}"); | ||
} else if (this.tokens.matches1(tt.jsxTagEnd)) { | ||
// Tag with children. | ||
this.tokens.removeToken(); | ||
this.processAutomaticChildrenAndEndProps(jsxRole); | ||
} else { | ||
throw new Error("Expected either /> or > at the end of the tag."); | ||
} | ||
// If a key was present, move it to its own arg. Note that moving code | ||
// like this will cause line numbers to get out of sync within the JSX | ||
// element if the key expression has a newline in it. This is unfortunate, | ||
// but hopefully should be rare. | ||
if (keyCode) { | ||
this.tokens.appendCode(`, ${keyCode}`); | ||
} | ||
} | ||
return this.filenameVarName; | ||
if (!this.options.production) { | ||
// If the key wasn't already added, add it now so we can correctly set | ||
// positional args for jsxDEV. | ||
if (keyCode === null) { | ||
this.tokens.appendCode(", void 0"); | ||
} | ||
this.tokens.appendCode(`, ${isStatic}, ${this.getDevSource(elementLocationCode)}, this`); | ||
} | ||
// We're at the close-tag or the end of a self-closing tag, so remove | ||
// everything else and close the function call. | ||
this.tokens.removeInitialToken(); | ||
while (!this.tokens.matches1(tt.jsxTagEnd)) { | ||
this.tokens.removeToken(); | ||
} | ||
this.tokens.replaceToken(")"); | ||
} | ||
processProps(firstTokenStart) { | ||
const lineNumber = this.getLineNumberForIndex(firstTokenStart); | ||
const devProps = this.options.production | ||
? "" | ||
: `__self: this, __source: {fileName: ${this.getFilenameVarName()}, lineNumber: ${lineNumber}}`; | ||
if (!this.tokens.matches1(tt.jsxName) && !this.tokens.matches1(tt.braceL)) { | ||
if (devProps) { | ||
this.tokens.appendCode(`, {${devProps}}`); | ||
/** | ||
* Convert the current JSX element to a createElement call. In the classic | ||
* runtime, this is the only case. In the automatic runtime, this is called | ||
* as a fallback in some situations. | ||
* | ||
* Example: | ||
* <div a={1} key={2}>Hello{x}</div> | ||
* becomes | ||
* React.createElement('div', {a: 1, key: 2}, "Hello", x) | ||
*/ | ||
transformTagToCreateElement(elementLocationCode) { | ||
// First tag is always jsxTagStart. | ||
this.tokens.replaceToken(this.getCreateElementInvocationCode()); | ||
if (this.tokens.matches1(tt.jsxTagEnd)) { | ||
// Fragment syntax. | ||
this.tokens.replaceToken(`${this.getFragmentCode()}, null`); | ||
this.processChildren(true); | ||
} else { | ||
// Normal open tag or self-closing tag. | ||
this.processTagIntro(); | ||
this.processPropsObjectWithDevInfo(elementLocationCode); | ||
if (this.tokens.matches2(tt.slash, tt.jsxTagEnd)) { | ||
// Self-closing tag; no children to process. | ||
} else if (this.tokens.matches1(tt.jsxTagEnd)) { | ||
// Tag with children and a close-tag; process the children as args. | ||
this.tokens.removeToken(); | ||
this.processChildren(true); | ||
} else { | ||
this.tokens.appendCode(`, null`); | ||
throw new Error("Expected either /> or > at the end of the tag."); | ||
} | ||
return; | ||
} | ||
this.tokens.appendCode(`, {`); | ||
while (true) { | ||
if (this.tokens.matches2(tt.jsxName, tt.eq)) { | ||
this.processPropKeyName(); | ||
this.tokens.replaceToken(": "); | ||
if (this.tokens.matches1(tt.braceL)) { | ||
this.tokens.replaceToken(""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
} else if (this.tokens.matches1(tt.jsxTagStart)) { | ||
this.processJSXTag(); | ||
} else { | ||
this.processStringPropValue(); | ||
} | ||
} else if (this.tokens.matches1(tt.jsxName)) { | ||
this.processPropKeyName(); | ||
this.tokens.appendCode(": true"); | ||
} else if (this.tokens.matches1(tt.braceL)) { | ||
this.tokens.replaceToken(""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
// We're at the close-tag or the end of a self-closing tag, so remove | ||
// everything else and close the function call. | ||
this.tokens.removeInitialToken(); | ||
while (!this.tokens.matches1(tt.jsxTagEnd)) { | ||
this.tokens.removeToken(); | ||
} | ||
this.tokens.replaceToken(")"); | ||
} | ||
/** | ||
* Get the code for the relevant function for this context: jsx, jsxs, | ||
* or jsxDEV. The following open-paren is included as well. | ||
* | ||
* These functions are only used for the automatic runtime, so they are always | ||
* auto-imported, but the auto-import will be either CJS or ESM based on the | ||
* target module format. | ||
*/ | ||
getJSXFuncInvocationCode(isStatic) { | ||
if (this.options.production) { | ||
if (isStatic) { | ||
return this.claimAutoImportedFuncInvocation("jsxs", "/jsx-runtime"); | ||
} else { | ||
break; | ||
return this.claimAutoImportedFuncInvocation("jsx", "/jsx-runtime"); | ||
} | ||
this.tokens.appendCode(","); | ||
} else { | ||
return this.claimAutoImportedFuncInvocation("jsxDEV", "/jsx-dev-runtime"); | ||
} | ||
if (devProps) { | ||
this.tokens.appendCode(` ${devProps}}`); | ||
} | ||
/** | ||
* Return the code to use for the createElement function, e.g. | ||
* `React.createElement`, including the following open-paren. | ||
* | ||
* This is the main function to use for the classic runtime. For the | ||
* automatic runtime, this function is used as a fallback function to | ||
* preserve behavior when there is a prop spread followed by an explicit | ||
* key. In that automatic runtime case, the function should be automatically | ||
* imported. | ||
*/ | ||
getCreateElementInvocationCode() { | ||
if (this.isAutomaticRuntime) { | ||
return this.claimAutoImportedFuncInvocation("createElement", ""); | ||
} else { | ||
this.tokens.appendCode("}"); | ||
const {jsxPragmaInfo} = this; | ||
const resolvedPragmaBaseName = this.importProcessor | ||
? this.importProcessor.getIdentifierReplacement(jsxPragmaInfo.base) || jsxPragmaInfo.base | ||
: jsxPragmaInfo.base; | ||
return `${resolvedPragmaBaseName}${jsxPragmaInfo.suffix}(`; | ||
} | ||
} | ||
processPropKeyName() { | ||
const keyName = this.tokens.identifierName(); | ||
if (keyName.includes("-")) { | ||
this.tokens.replaceToken(`'${keyName}'`); | ||
/** | ||
* Return the code to use as the component when compiling a shorthand | ||
* fragment, e.g. `React.Fragment`. | ||
* | ||
* This may be called from either the classic or automatic runtime, and | ||
* the value should be auto-imported for the automatic runtime. | ||
*/ | ||
getFragmentCode() { | ||
if (this.isAutomaticRuntime) { | ||
return this.claimAutoImportedName( | ||
"Fragment", | ||
this.options.production ? "/jsx-runtime" : "/jsx-dev-runtime", | ||
); | ||
} else { | ||
this.tokens.copyToken(); | ||
const {jsxPragmaInfo} = this; | ||
const resolvedFragmentPragmaBaseName = this.importProcessor | ||
? this.importProcessor.getIdentifierReplacement(jsxPragmaInfo.fragmentBase) || | ||
jsxPragmaInfo.fragmentBase | ||
: jsxPragmaInfo.fragmentBase; | ||
return resolvedFragmentPragmaBaseName + jsxPragmaInfo.fragmentSuffix; | ||
} | ||
} | ||
processStringPropValue() { | ||
const token = this.tokens.currentToken(); | ||
const valueCode = this.tokens.code.slice(token.start + 1, token.end - 1); | ||
const replacementCode = formatJSXTextReplacement(valueCode); | ||
const literalCode = formatJSXStringValueLiteral(valueCode); | ||
this.tokens.replaceToken(literalCode + replacementCode); | ||
/** | ||
* Return code that invokes the given function. | ||
* | ||
* When the imports transform is enabled, use the CJSImportTransformer | ||
* strategy of using `.call(void 0, ...` to avoid passing a `this` value in a | ||
* situation that would otherwise look like a method call. | ||
*/ | ||
claimAutoImportedFuncInvocation(funcName, importPathSuffix) { | ||
const funcCode = this.claimAutoImportedName(funcName, importPathSuffix); | ||
if (this.importProcessor) { | ||
return `${funcCode}.call(void 0, `; | ||
} else { | ||
return `${funcCode}(`; | ||
} | ||
} | ||
claimAutoImportedName(funcName, importPathSuffix) { | ||
if (this.importProcessor) { | ||
// CJS mode: claim a name for the module and mark it for import. | ||
const path = this.jsxImportSource + importPathSuffix; | ||
if (!this.cjsAutomaticModuleNameResolutions[path]) { | ||
this.cjsAutomaticModuleNameResolutions[path] = | ||
this.importProcessor.getFreeIdentifierForPath(path); | ||
} | ||
return `${this.cjsAutomaticModuleNameResolutions[path]}.${funcName}`; | ||
} else { | ||
// ESM mode: claim a name for this function and add it to the names that | ||
// should be auto-imported when the prefix is generated. | ||
if (!this.esmAutomaticImportNameResolutions[funcName]) { | ||
this.esmAutomaticImportNameResolutions[funcName] = this.nameManager.claimFreeName( | ||
`_${funcName}`, | ||
); | ||
} | ||
return this.esmAutomaticImportNameResolutions[funcName]; | ||
} | ||
} | ||
/** | ||
@@ -167,4 +356,147 @@ * Process the first part of a tag, before any props. | ||
processChildren() { | ||
/** | ||
* Starting at the beginning of the props, add the props argument to | ||
* React.createElement, including the comma before it. | ||
*/ | ||
processPropsObjectWithDevInfo(elementLocationCode) { | ||
const devProps = this.options.production | ||
? "" | ||
: `__self: this, __source: ${this.getDevSource(elementLocationCode)}`; | ||
if (!this.tokens.matches1(tt.jsxName) && !this.tokens.matches1(tt.braceL)) { | ||
if (devProps) { | ||
this.tokens.appendCode(`, {${devProps}}`); | ||
} else { | ||
this.tokens.appendCode(`, null`); | ||
} | ||
return; | ||
} | ||
this.tokens.appendCode(`, {`); | ||
this.processProps(false); | ||
if (devProps) { | ||
this.tokens.appendCode(` ${devProps}}`); | ||
} else { | ||
this.tokens.appendCode("}"); | ||
} | ||
} | ||
/** | ||
* Transform the core part of the props, assuming that a { has already been | ||
* inserted before us and that a } will be inserted after us. | ||
* | ||
* If extractKeyCode is true (i.e. when using any jsx... function), any prop | ||
* named "key" has its code captured and returned rather than being emitted to | ||
* the output code. This shifts line numbers, and emitting the code later will | ||
* correct line numbers again. If no key is found or if extractKeyCode is | ||
* false, this function returns null. | ||
*/ | ||
processProps(extractKeyCode) { | ||
let keyCode = null; | ||
while (true) { | ||
if (this.tokens.matches2(tt.jsxName, tt.eq)) { | ||
// This is a regular key={value} or key="value" prop. | ||
const propName = this.tokens.identifierName(); | ||
if (extractKeyCode && propName === "key") { | ||
if (keyCode !== null) { | ||
// The props list has multiple keys. Different implementations are | ||
// inconsistent about what to do here: as of this writing, Babel and | ||
// swc keep the *last* key and completely remove the rest, while | ||
// TypeScript uses the *first* key and leaves the others as regular | ||
// props. The React team collaborated with Babel on the | ||
// implementation of this behavior, so presumably the Babel behavior | ||
// is the one to use. | ||
// Since we won't ever be emitting the previous key code, we need to | ||
// at least emit its newlines here so that the line numbers match up | ||
// in the long run. | ||
this.tokens.appendCode(keyCode.replace(/[^\n]/g, "")); | ||
} | ||
// key | ||
this.tokens.removeToken(); | ||
// = | ||
this.tokens.removeToken(); | ||
const snapshot = this.tokens.snapshot(); | ||
this.processPropValue(); | ||
keyCode = this.tokens.dangerouslyGetAndRemoveCodeSinceSnapshot(snapshot); | ||
// Don't add a comma | ||
continue; | ||
} else { | ||
this.processPropName(propName); | ||
this.tokens.replaceToken(": "); | ||
this.processPropValue(); | ||
} | ||
} else if (this.tokens.matches1(tt.jsxName)) { | ||
// This is a shorthand prop like <input disabled />. | ||
const propName = this.tokens.identifierName(); | ||
this.processPropName(propName); | ||
this.tokens.appendCode(": true"); | ||
} else if (this.tokens.matches1(tt.braceL)) { | ||
// This is prop spread, like <div {...getProps()}>, which we can pass | ||
// through fairly directly as an object spread. | ||
this.tokens.replaceToken(""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
} else { | ||
break; | ||
} | ||
this.tokens.appendCode(","); | ||
} | ||
return keyCode; | ||
} | ||
processPropName(propName) { | ||
if (propName.includes("-")) { | ||
this.tokens.replaceToken(`'${propName}'`); | ||
} else { | ||
this.tokens.copyToken(); | ||
} | ||
} | ||
processPropValue() { | ||
if (this.tokens.matches1(tt.braceL)) { | ||
this.tokens.replaceToken(""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
} else if (this.tokens.matches1(tt.jsxTagStart)) { | ||
this.processJSXTag(); | ||
} else { | ||
this.processStringPropValue(); | ||
} | ||
} | ||
processStringPropValue() { | ||
const token = this.tokens.currentToken(); | ||
const valueCode = this.tokens.code.slice(token.start + 1, token.end - 1); | ||
const replacementCode = formatJSXTextReplacement(valueCode); | ||
const literalCode = formatJSXStringValueLiteral(valueCode); | ||
this.tokens.replaceToken(literalCode + replacementCode); | ||
} | ||
/** | ||
* Starting in the middle of the props object literal, produce an additional | ||
* prop for the children and close the object literal. | ||
*/ | ||
processAutomaticChildrenAndEndProps(jsxRole) { | ||
if (jsxRole === JSXRole.StaticChildren) { | ||
this.tokens.appendCode(" children: ["); | ||
this.processChildren(false); | ||
this.tokens.appendCode("]}"); | ||
} else { | ||
// The parser information tells us whether we will see a real child or if | ||
// all remaining children (if any) will resolve to empty. If there are no | ||
// non-empty children, don't emit a children prop at all, but still | ||
// process children so that we properly transform the code into nothing. | ||
if (jsxRole === JSXRole.OneChild) { | ||
this.tokens.appendCode(" children: "); | ||
} | ||
this.processChildren(false); | ||
this.tokens.appendCode("}"); | ||
} | ||
} | ||
/** | ||
* Transform children into a comma-separated list, which will be either | ||
* arguments to createElement or array elements of a children prop. | ||
*/ | ||
processChildren(needsInitialComma) { | ||
let needsComma = needsInitialComma; | ||
while (true) { | ||
if (this.tokens.matches2(tt.jsxTagStart, tt.slash)) { | ||
@@ -174,2 +506,3 @@ // Closing tag, so no more children. | ||
} | ||
let didEmitElement = false; | ||
if (this.tokens.matches1(tt.braceL)) { | ||
@@ -183,19 +516,30 @@ if (this.tokens.matches2(tt.braceL, tt.braceR)) { | ||
// Interpolated expression. | ||
this.tokens.replaceToken(", "); | ||
this.tokens.replaceToken(needsComma ? ", " : ""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
didEmitElement = true; | ||
} | ||
} else if (this.tokens.matches1(tt.jsxTagStart)) { | ||
// Child JSX element | ||
this.tokens.appendCode(", "); | ||
this.tokens.appendCode(needsComma ? ", " : ""); | ||
this.processJSXTag(); | ||
} else if (this.tokens.matches1(tt.jsxText)) { | ||
this.processChildTextElement(); | ||
didEmitElement = true; | ||
} else if (this.tokens.matches1(tt.jsxText) || this.tokens.matches1(tt.jsxEmptyText)) { | ||
didEmitElement = this.processChildTextElement(needsComma); | ||
} else { | ||
throw new Error("Unexpected token when processing JSX children."); | ||
} | ||
if (didEmitElement) { | ||
needsComma = true; | ||
} | ||
} | ||
} | ||
processChildTextElement() { | ||
/** | ||
* Turn a JSX text element into a string literal, or nothing at all if the JSX | ||
* text resolves to the empty string. | ||
* | ||
* Returns true if a string literal is emitted, false otherwise. | ||
*/ | ||
processChildTextElement(needsComma) { | ||
const token = this.tokens.currentToken(); | ||
@@ -207,52 +551,18 @@ const valueCode = this.tokens.code.slice(token.start, token.end); | ||
this.tokens.replaceToken(replacementCode); | ||
return false; | ||
} else { | ||
this.tokens.replaceToken(`, ${literalCode}${replacementCode}`); | ||
this.tokens.replaceToken(`${needsComma ? ", " : ""}${literalCode}${replacementCode}`); | ||
return true; | ||
} | ||
} | ||
processJSXTag() { | ||
const {jsxPragmaInfo} = this; | ||
const resolvedPragmaBaseName = this.importProcessor | ||
? this.importProcessor.getIdentifierReplacement(jsxPragmaInfo.base) || jsxPragmaInfo.base | ||
: jsxPragmaInfo.base; | ||
const firstTokenStart = this.tokens.currentToken().start; | ||
// First tag is always jsxTagStart. | ||
this.tokens.replaceToken(`${resolvedPragmaBaseName}${jsxPragmaInfo.suffix}(`); | ||
getDevSource(elementLocationCode) { | ||
return `{fileName: ${this.getFilenameVarName()}, ${elementLocationCode}}`; | ||
} | ||
if (this.tokens.matches1(tt.jsxTagEnd)) { | ||
// Fragment syntax. | ||
const resolvedFragmentPragmaBaseName = this.importProcessor | ||
? this.importProcessor.getIdentifierReplacement(jsxPragmaInfo.fragmentBase) || | ||
jsxPragmaInfo.fragmentBase | ||
: jsxPragmaInfo.fragmentBase; | ||
this.tokens.replaceToken( | ||
`${resolvedFragmentPragmaBaseName}${jsxPragmaInfo.fragmentSuffix}, null`, | ||
); | ||
// Tag with children. | ||
this.processChildren(); | ||
while (!this.tokens.matches1(tt.jsxTagEnd)) { | ||
this.tokens.replaceToken(""); | ||
} | ||
this.tokens.replaceToken(")"); | ||
} else { | ||
// Normal open tag or self-closing tag. | ||
this.processTagIntro(); | ||
this.processProps(firstTokenStart); | ||
if (this.tokens.matches2(tt.slash, tt.jsxTagEnd)) { | ||
// Self-closing tag. | ||
this.tokens.replaceToken(""); | ||
this.tokens.replaceToken(")"); | ||
} else if (this.tokens.matches1(tt.jsxTagEnd)) { | ||
this.tokens.replaceToken(""); | ||
// Tag with children. | ||
this.processChildren(); | ||
while (!this.tokens.matches1(tt.jsxTagEnd)) { | ||
this.tokens.replaceToken(""); | ||
} | ||
this.tokens.replaceToken(")"); | ||
} else { | ||
throw new Error("Expected either /> or > at the end of the tag."); | ||
} | ||
getFilenameVarName() { | ||
if (!this.filenameVarName) { | ||
this.filenameVarName = this.nameManager.claimFreeName("_jsxFileName"); | ||
} | ||
return this.filenameVarName; | ||
} | ||
@@ -259,0 +569,0 @@ } |
@@ -33,3 +33,3 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var _CJSImportProcessor = require('./CJSImportProcessor'); var _CJSImportProcessor2 = _interopRequireDefault(_CJSImportProcessor); | ||
function getVersion() { | ||
return "3.25.0"; | ||
return "3.26.0"; | ||
} exports.getVersion = getVersion; | ||
@@ -36,0 +36,0 @@ |
@@ -22,4 +22,10 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }/** | ||
transforms: t.array("Transform"), | ||
disableESTransforms: t.opt("boolean"), | ||
jsxRuntime: t.opt(t.union(t.lit("classic"), t.lit("automatic"))), | ||
production: t.opt("boolean"), | ||
jsxImportSource: t.opt("string"), | ||
jsxPragma: t.opt("string"), | ||
jsxFragmentPragma: t.opt("string"), | ||
preserveDynamicImport: t.opt("boolean"), | ||
injectCreateRequireForImportRequire: t.opt("boolean"), | ||
enableLegacyTypeScriptModuleInterop: t.opt("boolean"), | ||
@@ -29,6 +35,2 @@ enableLegacyBabel5ModuleInterop: t.opt("boolean"), | ||
filePath: t.opt("string"), | ||
production: t.opt("boolean"), | ||
disableESTransforms: t.opt("boolean"), | ||
preserveDynamicImport: t.opt("boolean"), | ||
injectCreateRequireForImportRequire: t.opt("boolean"), | ||
}); exports.Options = Options; | ||
@@ -35,0 +37,0 @@ |
@@ -72,4 +72,26 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var _tsinterfacechecker = require('ts-interface-checker'); | ||
function validateOptions(options) { | ||
OptionsChecker.strictCheck(options); | ||
} exports.validateOptions = validateOptions; |
@@ -10,2 +10,3 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); | ||
var _index = require('../../tokenizer/index'); | ||
@@ -20,5 +21,23 @@ var _types = require('../../tokenizer/types'); | ||
// Reads inline JSX contents token. | ||
/** | ||
* 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() { | ||
for (;;) { | ||
let sawNewline = false; | ||
let sawNonWhitespace = false; | ||
while (true) { | ||
if (_base.state.pos >= _base.input.length) { | ||
@@ -30,21 +49,27 @@ _util.unexpected.call(void 0, "Unterminated JSX contents"); | ||
const ch = _base.input.charCodeAt(_base.state.pos); | ||
switch (ch) { | ||
case _charcodes.charCodes.lessThan: | ||
case _charcodes.charCodes.leftCurlyBrace: | ||
if (_base.state.pos === _base.state.start) { | ||
if (ch === _charcodes.charCodes.lessThan) { | ||
_base.state.pos++; | ||
_index.finishToken.call(void 0, _types.TokenType.jsxTagStart); | ||
return; | ||
} | ||
_index.getTokenFromCode.call(void 0, ch); | ||
if (ch === _charcodes.charCodes.lessThan || ch === _charcodes.charCodes.leftCurlyBrace) { | ||
if (_base.state.pos === _base.state.start) { | ||
if (ch === _charcodes.charCodes.lessThan) { | ||
_base.state.pos++; | ||
_index.finishToken.call(void 0, _types.TokenType.jsxTagStart); | ||
return; | ||
} | ||
_index.getTokenFromCode.call(void 0, ch); | ||
return; | ||
} | ||
if (sawNewline && !sawNonWhitespace) { | ||
_index.finishToken.call(void 0, _types.TokenType.jsxEmptyText); | ||
} else { | ||
_index.finishToken.call(void 0, _types.TokenType.jsxText); | ||
return; | ||
} | ||
return; | ||
} | ||
default: | ||
_base.state.pos++; | ||
// This is part of JSX text. | ||
if (ch === _charcodes.charCodes.lineFeed) { | ||
sawNewline = true; | ||
} else if (ch !== _charcodes.charCodes.space && ch !== _charcodes.charCodes.carriageReturn && ch !== _charcodes.charCodes.tab) { | ||
sawNonWhitespace = true; | ||
} | ||
_base.state.pos++; | ||
} | ||
@@ -122,3 +147,3 @@ } | ||
_index.next.call(void 0, ); | ||
jsxParseExpressionContainer(); | ||
_expression.parseExpression.call(void 0, ); | ||
nextJSXTagToken(); | ||
@@ -141,6 +166,2 @@ return; | ||
function jsxParseEmptyExpression() { | ||
// Do nothing. | ||
} | ||
// Parse JSX spread child, after already processing the { | ||
@@ -153,32 +174,6 @@ // Does not parse the closing } | ||
// Parses JSX expression enclosed into curly brackets, after already processing the { | ||
// Does not parse the closing } | ||
function jsxParseExpressionContainer() { | ||
if (_index.match.call(void 0, _types.TokenType.braceR)) { | ||
jsxParseEmptyExpression(); | ||
} else { | ||
_expression.parseExpression.call(void 0, ); | ||
} | ||
} | ||
// Parses following JSX attribute name-value pair. | ||
function jsxParseAttribute() { | ||
if (_index.eat.call(void 0, _types.TokenType.braceL)) { | ||
_util.expect.call(void 0, _types.TokenType.ellipsis); | ||
_expression.parseMaybeAssign.call(void 0, ); | ||
// } | ||
nextJSXTagToken(); | ||
return; | ||
} | ||
jsxParseNamespacedName(_index.IdentifierRole.ObjectKey); | ||
if (_index.match.call(void 0, _types.TokenType.eq)) { | ||
nextJSXTagToken(); | ||
jsxParseAttributeValue(); | ||
} | ||
} | ||
// Parses JSX opening tag starting after "<". | ||
// Returns true if the tag was self-closing. | ||
// Does not parse the last token. | ||
function jsxParseOpeningElement() { | ||
function jsxParseOpeningElement(initialTokenIndex) { | ||
if (_index.match.call(void 0, _types.TokenType.jsxTagEnd)) { | ||
@@ -192,4 +187,26 @@ // This is an open-fragment. | ||
} | ||
let hasSeenPropSpread = false; | ||
while (!_index.match.call(void 0, _types.TokenType.slash) && !_index.match.call(void 0, _types.TokenType.jsxTagEnd) && !_base.state.error) { | ||
jsxParseAttribute(); | ||
if (_index.eat.call(void 0, _types.TokenType.braceL)) { | ||
hasSeenPropSpread = true; | ||
_util.expect.call(void 0, _types.TokenType.ellipsis); | ||
_expression.parseMaybeAssign.call(void 0, ); | ||
// } | ||
nextJSXTagToken(); | ||
continue; | ||
} | ||
if ( | ||
hasSeenPropSpread && | ||
_base.state.end - _base.state.start === 3 && | ||
_base.input.charCodeAt(_base.state.start) === _charcodes.charCodes.lowercaseK && | ||
_base.input.charCodeAt(_base.state.start + 1) === _charcodes.charCodes.lowercaseE && | ||
_base.input.charCodeAt(_base.state.start + 2) === _charcodes.charCodes.lowercaseY | ||
) { | ||
_base.state.tokens[initialTokenIndex].jsxRole = _index.JSXRole.KeyAfterPropSpread; | ||
} | ||
jsxParseNamespacedName(_index.IdentifierRole.ObjectKey); | ||
if (_index.match.call(void 0, _types.TokenType.eq)) { | ||
nextJSXTagToken(); | ||
jsxParseAttributeValue(); | ||
} | ||
} | ||
@@ -218,3 +235,6 @@ const isSelfClosing = _index.match.call(void 0, _types.TokenType.slash); | ||
function jsxParseElementAt() { | ||
const isSelfClosing = jsxParseOpeningElement(); | ||
const initialTokenIndex = _base.state.tokens.length - 1; | ||
_base.state.tokens[initialTokenIndex].jsxRole = _index.JSXRole.NoChildren; | ||
let numExplicitChildren = 0; | ||
const isSelfClosing = jsxParseOpeningElement(initialTokenIndex); | ||
if (!isSelfClosing) { | ||
@@ -229,4 +249,15 @@ nextJSXExprToken(); | ||
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 (_base.state.tokens[initialTokenIndex].jsxRole !== _index.JSXRole.KeyAfterPropSpread) { | ||
if (numExplicitChildren === 1) { | ||
_base.state.tokens[initialTokenIndex].jsxRole = _index.JSXRole.OneChild; | ||
} else if (numExplicitChildren > 1) { | ||
_base.state.tokens[initialTokenIndex].jsxRole = _index.JSXRole.StaticChildren; | ||
} | ||
} | ||
return; | ||
} | ||
numExplicitChildren++; | ||
jsxParseElementAt(); | ||
@@ -237,5 +268,10 @@ nextJSXExprToken(); | ||
case _types.TokenType.jsxText: | ||
numExplicitChildren++; | ||
nextJSXExprToken(); | ||
break; | ||
case _types.TokenType.jsxEmptyText: | ||
nextJSXExprToken(); | ||
break; | ||
case _types.TokenType.braceL: | ||
@@ -246,4 +282,13 @@ _index.next.call(void 0, ); | ||
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 { | ||
jsxParseExpressionContainer(); | ||
// If we see {}, this is an empty pseudo-expression that doesn't | ||
// count as a child. | ||
if (!_index.match.call(void 0, _types.TokenType.braceR)) { | ||
numExplicitChildren++; | ||
_expression.parseExpression.call(void 0, ); | ||
} | ||
nextJSXExprToken(); | ||
@@ -250,0 +295,0 @@ } |
@@ -30,2 +30,22 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }/* eslint max-len: 0 */ | ||
/** | ||
* Extra information on jsxTagStart tokens, used to determine which of the three | ||
* jsx functions are called in the automatic transform. | ||
*/ | ||
var JSXRole; (function (JSXRole) { | ||
// The element is self-closing or has a body that resolves to empty. We | ||
// shouldn't emit children at all in this case. | ||
const NoChildren = 0; JSXRole[JSXRole["NoChildren"] = NoChildren] = "NoChildren"; | ||
// The element has a single explicit child, which might still be an arbitrary | ||
// expression like an array. We should emit that expression as the children. | ||
const OneChild = NoChildren + 1; JSXRole[JSXRole["OneChild"] = OneChild] = "OneChild"; | ||
// The element has at least two explicitly-specified children or has spread | ||
// children, so child positions are assumed to be "static". We should wrap | ||
// these children in an array. | ||
const StaticChildren = OneChild + 1; JSXRole[JSXRole["StaticChildren"] = StaticChildren] = "StaticChildren"; | ||
// The element has a prop named "key" after a prop spread, so we should fall | ||
// back to the createElement function. | ||
const KeyAfterPropSpread = StaticChildren + 1; JSXRole[JSXRole["KeyAfterPropSpread"] = KeyAfterPropSpread] = "KeyAfterPropSpread"; | ||
})(JSXRole || (exports.JSXRole = JSXRole = {})); | ||
function isDeclaration(token) { | ||
@@ -101,2 +121,3 @@ const role = token.identifierRole; | ||
this.identifierRole = null; | ||
this.jsxRole = null; | ||
this.shadowsGlobal = false; | ||
@@ -122,2 +143,3 @@ this.isAsyncOperation = false; | ||
// Initially false for all tokens, then may be computed in a follow-up step that does scope | ||
@@ -124,0 +146,0 @@ // analysis. |
@@ -72,58 +72,59 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// Generated file, do not edit! Run "yarn generate" to re-generate this file. | ||
const jsxText = 56320; TokenType[TokenType["jsxText"] = jsxText] = "jsxText"; // jsxText | ||
const jsxTagStart = 57856; TokenType[TokenType["jsxTagStart"] = jsxTagStart] = "jsxTagStart"; // jsxTagStart startsExpr | ||
const jsxTagEnd = 58368; TokenType[TokenType["jsxTagEnd"] = jsxTagEnd] = "jsxTagEnd"; // jsxTagEnd | ||
const typeParameterStart = 59904; TokenType[TokenType["typeParameterStart"] = typeParameterStart] = "typeParameterStart"; // typeParameterStart startsExpr | ||
const nonNullAssertion = 60416; TokenType[TokenType["nonNullAssertion"] = nonNullAssertion] = "nonNullAssertion"; // nonNullAssertion | ||
const _break = 61456; TokenType[TokenType["_break"] = _break] = "_break"; // break keyword | ||
const _case = 62480; TokenType[TokenType["_case"] = _case] = "_case"; // case keyword | ||
const _catch = 63504; TokenType[TokenType["_catch"] = _catch] = "_catch"; // catch keyword | ||
const _continue = 64528; TokenType[TokenType["_continue"] = _continue] = "_continue"; // continue keyword | ||
const _debugger = 65552; TokenType[TokenType["_debugger"] = _debugger] = "_debugger"; // debugger keyword | ||
const _default = 66576; TokenType[TokenType["_default"] = _default] = "_default"; // default keyword | ||
const _do = 67600; TokenType[TokenType["_do"] = _do] = "_do"; // do keyword | ||
const _else = 68624; TokenType[TokenType["_else"] = _else] = "_else"; // else keyword | ||
const _finally = 69648; TokenType[TokenType["_finally"] = _finally] = "_finally"; // finally keyword | ||
const _for = 70672; TokenType[TokenType["_for"] = _for] = "_for"; // for keyword | ||
const _function = 72208; TokenType[TokenType["_function"] = _function] = "_function"; // function keyword startsExpr | ||
const _if = 72720; TokenType[TokenType["_if"] = _if] = "_if"; // if keyword | ||
const _return = 73744; TokenType[TokenType["_return"] = _return] = "_return"; // return keyword | ||
const _switch = 74768; TokenType[TokenType["_switch"] = _switch] = "_switch"; // switch keyword | ||
const _throw = 76432; TokenType[TokenType["_throw"] = _throw] = "_throw"; // throw keyword prefix startsExpr | ||
const _try = 76816; TokenType[TokenType["_try"] = _try] = "_try"; // try keyword | ||
const _var = 77840; TokenType[TokenType["_var"] = _var] = "_var"; // var keyword | ||
const _let = 78864; TokenType[TokenType["_let"] = _let] = "_let"; // let keyword | ||
const _const = 79888; TokenType[TokenType["_const"] = _const] = "_const"; // const keyword | ||
const _while = 80912; TokenType[TokenType["_while"] = _while] = "_while"; // while keyword | ||
const _with = 81936; TokenType[TokenType["_with"] = _with] = "_with"; // with keyword | ||
const _new = 83472; TokenType[TokenType["_new"] = _new] = "_new"; // new keyword startsExpr | ||
const _this = 84496; TokenType[TokenType["_this"] = _this] = "_this"; // this keyword startsExpr | ||
const _super = 85520; TokenType[TokenType["_super"] = _super] = "_super"; // super keyword startsExpr | ||
const _class = 86544; TokenType[TokenType["_class"] = _class] = "_class"; // class keyword startsExpr | ||
const _extends = 87056; TokenType[TokenType["_extends"] = _extends] = "_extends"; // extends keyword | ||
const _export = 88080; TokenType[TokenType["_export"] = _export] = "_export"; // export keyword | ||
const _import = 89616; TokenType[TokenType["_import"] = _import] = "_import"; // import keyword startsExpr | ||
const _yield = 90640; TokenType[TokenType["_yield"] = _yield] = "_yield"; // yield keyword startsExpr | ||
const _null = 91664; TokenType[TokenType["_null"] = _null] = "_null"; // null keyword startsExpr | ||
const _true = 92688; TokenType[TokenType["_true"] = _true] = "_true"; // true keyword startsExpr | ||
const _false = 93712; TokenType[TokenType["_false"] = _false] = "_false"; // false keyword startsExpr | ||
const _in = 94232; TokenType[TokenType["_in"] = _in] = "_in"; // in prec:8 keyword | ||
const _instanceof = 95256; TokenType[TokenType["_instanceof"] = _instanceof] = "_instanceof"; // instanceof prec:8 keyword | ||
const _typeof = 96912; TokenType[TokenType["_typeof"] = _typeof] = "_typeof"; // typeof keyword prefix startsExpr | ||
const _void = 97936; TokenType[TokenType["_void"] = _void] = "_void"; // void keyword prefix startsExpr | ||
const _delete = 98960; TokenType[TokenType["_delete"] = _delete] = "_delete"; // delete keyword prefix startsExpr | ||
const _async = 99856; TokenType[TokenType["_async"] = _async] = "_async"; // async keyword startsExpr | ||
const _get = 100880; TokenType[TokenType["_get"] = _get] = "_get"; // get keyword startsExpr | ||
const _set = 101904; TokenType[TokenType["_set"] = _set] = "_set"; // set keyword startsExpr | ||
const _declare = 102928; TokenType[TokenType["_declare"] = _declare] = "_declare"; // declare keyword startsExpr | ||
const _readonly = 103952; TokenType[TokenType["_readonly"] = _readonly] = "_readonly"; // readonly keyword startsExpr | ||
const _abstract = 104976; TokenType[TokenType["_abstract"] = _abstract] = "_abstract"; // abstract keyword startsExpr | ||
const _static = 106000; TokenType[TokenType["_static"] = _static] = "_static"; // static keyword startsExpr | ||
const _public = 106512; TokenType[TokenType["_public"] = _public] = "_public"; // public keyword | ||
const _private = 107536; TokenType[TokenType["_private"] = _private] = "_private"; // private keyword | ||
const _protected = 108560; TokenType[TokenType["_protected"] = _protected] = "_protected"; // protected keyword | ||
const _override = 109584; TokenType[TokenType["_override"] = _override] = "_override"; // override keyword | ||
const _as = 111120; TokenType[TokenType["_as"] = _as] = "_as"; // as keyword startsExpr | ||
const _enum = 112144; TokenType[TokenType["_enum"] = _enum] = "_enum"; // enum keyword startsExpr | ||
const _type = 113168; TokenType[TokenType["_type"] = _type] = "_type"; // type keyword startsExpr | ||
const _implements = 114192; TokenType[TokenType["_implements"] = _implements] = "_implements"; // implements keyword startsExpr | ||
const jsxEmptyText = 57344; TokenType[TokenType["jsxEmptyText"] = jsxEmptyText] = "jsxEmptyText"; // jsxEmptyText | ||
const jsxTagStart = 58880; TokenType[TokenType["jsxTagStart"] = jsxTagStart] = "jsxTagStart"; // jsxTagStart startsExpr | ||
const jsxTagEnd = 59392; TokenType[TokenType["jsxTagEnd"] = jsxTagEnd] = "jsxTagEnd"; // jsxTagEnd | ||
const typeParameterStart = 60928; TokenType[TokenType["typeParameterStart"] = typeParameterStart] = "typeParameterStart"; // typeParameterStart startsExpr | ||
const nonNullAssertion = 61440; TokenType[TokenType["nonNullAssertion"] = nonNullAssertion] = "nonNullAssertion"; // nonNullAssertion | ||
const _break = 62480; TokenType[TokenType["_break"] = _break] = "_break"; // break keyword | ||
const _case = 63504; TokenType[TokenType["_case"] = _case] = "_case"; // case keyword | ||
const _catch = 64528; TokenType[TokenType["_catch"] = _catch] = "_catch"; // catch keyword | ||
const _continue = 65552; TokenType[TokenType["_continue"] = _continue] = "_continue"; // continue keyword | ||
const _debugger = 66576; TokenType[TokenType["_debugger"] = _debugger] = "_debugger"; // debugger keyword | ||
const _default = 67600; TokenType[TokenType["_default"] = _default] = "_default"; // default keyword | ||
const _do = 68624; TokenType[TokenType["_do"] = _do] = "_do"; // do keyword | ||
const _else = 69648; TokenType[TokenType["_else"] = _else] = "_else"; // else keyword | ||
const _finally = 70672; TokenType[TokenType["_finally"] = _finally] = "_finally"; // finally keyword | ||
const _for = 71696; TokenType[TokenType["_for"] = _for] = "_for"; // for keyword | ||
const _function = 73232; TokenType[TokenType["_function"] = _function] = "_function"; // function keyword startsExpr | ||
const _if = 73744; TokenType[TokenType["_if"] = _if] = "_if"; // if keyword | ||
const _return = 74768; TokenType[TokenType["_return"] = _return] = "_return"; // return keyword | ||
const _switch = 75792; TokenType[TokenType["_switch"] = _switch] = "_switch"; // switch keyword | ||
const _throw = 77456; TokenType[TokenType["_throw"] = _throw] = "_throw"; // throw keyword prefix startsExpr | ||
const _try = 77840; TokenType[TokenType["_try"] = _try] = "_try"; // try keyword | ||
const _var = 78864; TokenType[TokenType["_var"] = _var] = "_var"; // var keyword | ||
const _let = 79888; TokenType[TokenType["_let"] = _let] = "_let"; // let keyword | ||
const _const = 80912; TokenType[TokenType["_const"] = _const] = "_const"; // const keyword | ||
const _while = 81936; TokenType[TokenType["_while"] = _while] = "_while"; // while keyword | ||
const _with = 82960; TokenType[TokenType["_with"] = _with] = "_with"; // with keyword | ||
const _new = 84496; TokenType[TokenType["_new"] = _new] = "_new"; // new keyword startsExpr | ||
const _this = 85520; TokenType[TokenType["_this"] = _this] = "_this"; // this keyword startsExpr | ||
const _super = 86544; TokenType[TokenType["_super"] = _super] = "_super"; // super keyword startsExpr | ||
const _class = 87568; TokenType[TokenType["_class"] = _class] = "_class"; // class keyword startsExpr | ||
const _extends = 88080; TokenType[TokenType["_extends"] = _extends] = "_extends"; // extends keyword | ||
const _export = 89104; TokenType[TokenType["_export"] = _export] = "_export"; // export keyword | ||
const _import = 90640; TokenType[TokenType["_import"] = _import] = "_import"; // import keyword startsExpr | ||
const _yield = 91664; TokenType[TokenType["_yield"] = _yield] = "_yield"; // yield keyword startsExpr | ||
const _null = 92688; TokenType[TokenType["_null"] = _null] = "_null"; // null keyword startsExpr | ||
const _true = 93712; TokenType[TokenType["_true"] = _true] = "_true"; // true keyword startsExpr | ||
const _false = 94736; TokenType[TokenType["_false"] = _false] = "_false"; // false keyword startsExpr | ||
const _in = 95256; TokenType[TokenType["_in"] = _in] = "_in"; // in prec:8 keyword | ||
const _instanceof = 96280; TokenType[TokenType["_instanceof"] = _instanceof] = "_instanceof"; // instanceof prec:8 keyword | ||
const _typeof = 97936; TokenType[TokenType["_typeof"] = _typeof] = "_typeof"; // typeof keyword prefix startsExpr | ||
const _void = 98960; TokenType[TokenType["_void"] = _void] = "_void"; // void keyword prefix startsExpr | ||
const _delete = 99984; TokenType[TokenType["_delete"] = _delete] = "_delete"; // delete keyword prefix startsExpr | ||
const _async = 100880; TokenType[TokenType["_async"] = _async] = "_async"; // async keyword startsExpr | ||
const _get = 101904; TokenType[TokenType["_get"] = _get] = "_get"; // get keyword startsExpr | ||
const _set = 102928; TokenType[TokenType["_set"] = _set] = "_set"; // set keyword startsExpr | ||
const _declare = 103952; TokenType[TokenType["_declare"] = _declare] = "_declare"; // declare keyword startsExpr | ||
const _readonly = 104976; TokenType[TokenType["_readonly"] = _readonly] = "_readonly"; // readonly keyword startsExpr | ||
const _abstract = 106000; TokenType[TokenType["_abstract"] = _abstract] = "_abstract"; // abstract keyword startsExpr | ||
const _static = 107024; TokenType[TokenType["_static"] = _static] = "_static"; // static keyword startsExpr | ||
const _public = 107536; TokenType[TokenType["_public"] = _public] = "_public"; // public keyword | ||
const _private = 108560; TokenType[TokenType["_private"] = _private] = "_private"; // private keyword | ||
const _protected = 109584; TokenType[TokenType["_protected"] = _protected] = "_protected"; // protected keyword | ||
const _override = 110608; TokenType[TokenType["_override"] = _override] = "_override"; // override keyword | ||
const _as = 112144; TokenType[TokenType["_as"] = _as] = "_as"; // as keyword startsExpr | ||
const _enum = 113168; TokenType[TokenType["_enum"] = _enum] = "_enum"; // enum keyword startsExpr | ||
const _type = 114192; TokenType[TokenType["_type"] = _type] = "_type"; // type keyword startsExpr | ||
const _implements = 115216; TokenType[TokenType["_implements"] = _implements] = "_implements"; // implements keyword startsExpr | ||
})(TokenType || (exports.TokenType = TokenType = {})); | ||
@@ -244,2 +245,4 @@ function formatTokenType(tokenType) { | ||
return "jsxText"; | ||
case TokenType.jsxEmptyText: | ||
return "jsxEmptyText"; | ||
case TokenType.jsxTagStart: | ||
@@ -246,0 +249,0 @@ return "jsxTagStart"; |
@@ -451,3 +451,3 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true});/* eslint max-len: 0 */ | ||
if (_index3.match.call(void 0, _types3.TokenType.jsxText)) { | ||
if (_index3.match.call(void 0, _types3.TokenType.jsxText) || _index3.match.call(void 0, _types3.TokenType.jsxEmptyText)) { | ||
parseLiteral(); | ||
@@ -454,0 +454,0 @@ return false; |
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var charCodes; (function (charCodes) { | ||
const backSpace = 8; charCodes[charCodes["backSpace"] = backSpace] = "backSpace"; | ||
const lineFeed = 10; charCodes[charCodes["lineFeed"] = lineFeed] = "lineFeed"; // '\n' | ||
const tab = 9; charCodes[charCodes["tab"] = tab] = "tab"; // '\t' | ||
const carriageReturn = 13; charCodes[charCodes["carriageReturn"] = carriageReturn] = "carriageReturn"; // '\r' | ||
@@ -5,0 +6,0 @@ const shiftOut = 14; charCodes[charCodes["shiftOut"] = shiftOut] = "shiftOut"; |
@@ -36,4 +36,13 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
getResultCodeIndex() { | ||
return this.resultCode.length; | ||
/** | ||
* Remove and return the code generated since the snapshot, leaving the | ||
* current token position in-place. Unlike most TokenProcessor operations, | ||
* this operation can result in input/output line number mismatches because | ||
* the removed code may contain newlines, so this operation should be used | ||
* sparingly. | ||
*/ | ||
dangerouslyGetAndRemoveCodeSinceSnapshot(snapshot) { | ||
const result = this.resultCode.slice(snapshot.resultCode.length); | ||
this.resultCode = snapshot.resultCode; | ||
return result; | ||
} | ||
@@ -40,0 +49,0 @@ |
@@ -5,2 +5,3 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var _xhtml = require('../parser/plugins/jsx/xhtml'); var _xhtml2 = _interopRequireDefault(_xhtml); | ||
var _tokenizer = require('../parser/tokenizer'); | ||
var _types = require('../parser/tokenizer/types'); | ||
@@ -14,6 +15,18 @@ var _charcodes = require('../parser/util/charcodes'); | ||
class JSXTransformer extends _Transformer2.default { | ||
// State for calculating the line number of each JSX tag in development. | ||
__init() {this.lastLineNumber = 1} | ||
__init2() {this.lastIndex = 0} | ||
// In development, variable name holding the name of the current file. | ||
__init3() {this.filenameVarName = null} | ||
// Mapping of claimed names for imports in the automatic transform, e,g. | ||
// {jsx: "_jsx"}. This determines which imports to generate in the prefix. | ||
__init4() {this.esmAutomaticImportNameResolutions = {}} | ||
// When automatically adding imports in CJS mode, we store the variable name | ||
// holding the imported CJS module so we can require it in the prefix. | ||
__init5() {this.cjsAutomaticModuleNameResolutions = {}} | ||
@@ -27,4 +40,6 @@ constructor( | ||
) { | ||
super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.importProcessor = importProcessor;this.nameManager = nameManager;this.options = options;JSXTransformer.prototype.__init.call(this);JSXTransformer.prototype.__init2.call(this);JSXTransformer.prototype.__init3.call(this);; | ||
super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.importProcessor = importProcessor;this.nameManager = nameManager;this.options = options;JSXTransformer.prototype.__init.call(this);JSXTransformer.prototype.__init2.call(this);JSXTransformer.prototype.__init3.call(this);JSXTransformer.prototype.__init4.call(this);JSXTransformer.prototype.__init5.call(this);; | ||
this.jsxPragmaInfo = _getJSXPragmaInfo2.default.call(void 0, options); | ||
this.isAutomaticRuntime = options.jsxRuntime === "automatic"; | ||
this.jsxImportSource = options.jsxImportSource || "react"; | ||
} | ||
@@ -41,12 +56,52 @@ | ||
getPrefixCode() { | ||
let prefix = ""; | ||
if (this.filenameVarName) { | ||
return `const ${this.filenameVarName} = ${JSON.stringify(this.options.filePath || "")};`; | ||
prefix += `const ${this.filenameVarName} = ${JSON.stringify(this.options.filePath || "")};`; | ||
} | ||
if (this.isAutomaticRuntime) { | ||
if (this.importProcessor) { | ||
// CJS mode: emit require statements for all modules that were referenced. | ||
for (const [path, resolvedName] of Object.entries(this.cjsAutomaticModuleNameResolutions)) { | ||
prefix += `var ${resolvedName} = require("${path}");`; | ||
} | ||
} else { | ||
// ESM mode: consolidate and emit import statements for referenced names. | ||
const {createElement: createElementResolution, ...otherResolutions} = | ||
this.esmAutomaticImportNameResolutions; | ||
if (createElementResolution) { | ||
prefix += `import {createElement as ${createElementResolution}} from "${this.jsxImportSource}";`; | ||
} | ||
const importSpecifiers = Object.entries(otherResolutions) | ||
.map(([name, resolvedName]) => `${name} as ${resolvedName}`) | ||
.join(", "); | ||
if (importSpecifiers) { | ||
const importPath = | ||
this.jsxImportSource + (this.options.production ? "/jsx-runtime" : "/jsx-dev-runtime"); | ||
prefix += `import {${importSpecifiers}} from "${importPath}";`; | ||
} | ||
} | ||
} | ||
return prefix; | ||
} | ||
processJSXTag() { | ||
const {jsxRole, start} = this.tokens.currentToken(); | ||
// Calculate line number information at the very start (if in development | ||
// mode) so that the information is guaranteed to be queried in token order. | ||
const elementLocationCode = this.options.production ? null : this.getElementLocationCode(start); | ||
if (this.isAutomaticRuntime && jsxRole !== _tokenizer.JSXRole.KeyAfterPropSpread) { | ||
this.transformTagToJSXFunc(elementLocationCode, jsxRole); | ||
} else { | ||
return ""; | ||
this.transformTagToCreateElement(elementLocationCode); | ||
} | ||
} | ||
getElementLocationCode(firstTokenStart) { | ||
const lineNumber = this.getLineNumberForIndex(firstTokenStart); | ||
return `lineNumber: ${lineNumber}`; | ||
} | ||
/** | ||
* Lazily calculate line numbers to avoid unneeded work. We assume this is always called in | ||
* increasing order by index. | ||
* Get the line number for this source position. This is calculated lazily and | ||
* must be called in increasing order by index. | ||
*/ | ||
@@ -64,72 +119,206 @@ getLineNumberForIndex(index) { | ||
getFilenameVarName() { | ||
if (!this.filenameVarName) { | ||
this.filenameVarName = this.nameManager.claimFreeName("_jsxFileName"); | ||
/** | ||
* Convert the current JSX element to a call to jsx, jsxs, or jsxDEV. This is | ||
* the primary transformation for the automatic transform. | ||
* | ||
* Example: | ||
* <div a={1} key={2}>Hello{x}</div> | ||
* becomes | ||
* jsxs('div', {a: 1, children: ["Hello", x]}, 2) | ||
*/ | ||
transformTagToJSXFunc(elementLocationCode, jsxRole) { | ||
const isStatic = jsxRole === _tokenizer.JSXRole.StaticChildren; | ||
// First tag is always jsxTagStart. | ||
this.tokens.replaceToken(this.getJSXFuncInvocationCode(isStatic)); | ||
let keyCode = null; | ||
if (this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
// Fragment syntax. | ||
this.tokens.replaceToken(`${this.getFragmentCode()}, {`); | ||
this.processAutomaticChildrenAndEndProps(jsxRole); | ||
} else { | ||
// Normal open tag or self-closing tag. | ||
this.processTagIntro(); | ||
this.tokens.appendCode(", {"); | ||
keyCode = this.processProps(true); | ||
if (this.tokens.matches2(_types.TokenType.slash, _types.TokenType.jsxTagEnd)) { | ||
// Self-closing tag, no children to add, so close the props. | ||
this.tokens.appendCode("}"); | ||
} else if (this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
// Tag with children. | ||
this.tokens.removeToken(); | ||
this.processAutomaticChildrenAndEndProps(jsxRole); | ||
} else { | ||
throw new Error("Expected either /> or > at the end of the tag."); | ||
} | ||
// If a key was present, move it to its own arg. Note that moving code | ||
// like this will cause line numbers to get out of sync within the JSX | ||
// element if the key expression has a newline in it. This is unfortunate, | ||
// but hopefully should be rare. | ||
if (keyCode) { | ||
this.tokens.appendCode(`, ${keyCode}`); | ||
} | ||
} | ||
return this.filenameVarName; | ||
if (!this.options.production) { | ||
// If the key wasn't already added, add it now so we can correctly set | ||
// positional args for jsxDEV. | ||
if (keyCode === null) { | ||
this.tokens.appendCode(", void 0"); | ||
} | ||
this.tokens.appendCode(`, ${isStatic}, ${this.getDevSource(elementLocationCode)}, this`); | ||
} | ||
// We're at the close-tag or the end of a self-closing tag, so remove | ||
// everything else and close the function call. | ||
this.tokens.removeInitialToken(); | ||
while (!this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
this.tokens.removeToken(); | ||
} | ||
this.tokens.replaceToken(")"); | ||
} | ||
processProps(firstTokenStart) { | ||
const lineNumber = this.getLineNumberForIndex(firstTokenStart); | ||
const devProps = this.options.production | ||
? "" | ||
: `__self: this, __source: {fileName: ${this.getFilenameVarName()}, lineNumber: ${lineNumber}}`; | ||
if (!this.tokens.matches1(_types.TokenType.jsxName) && !this.tokens.matches1(_types.TokenType.braceL)) { | ||
if (devProps) { | ||
this.tokens.appendCode(`, {${devProps}}`); | ||
/** | ||
* Convert the current JSX element to a createElement call. In the classic | ||
* runtime, this is the only case. In the automatic runtime, this is called | ||
* as a fallback in some situations. | ||
* | ||
* Example: | ||
* <div a={1} key={2}>Hello{x}</div> | ||
* becomes | ||
* React.createElement('div', {a: 1, key: 2}, "Hello", x) | ||
*/ | ||
transformTagToCreateElement(elementLocationCode) { | ||
// First tag is always jsxTagStart. | ||
this.tokens.replaceToken(this.getCreateElementInvocationCode()); | ||
if (this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
// Fragment syntax. | ||
this.tokens.replaceToken(`${this.getFragmentCode()}, null`); | ||
this.processChildren(true); | ||
} else { | ||
// Normal open tag or self-closing tag. | ||
this.processTagIntro(); | ||
this.processPropsObjectWithDevInfo(elementLocationCode); | ||
if (this.tokens.matches2(_types.TokenType.slash, _types.TokenType.jsxTagEnd)) { | ||
// Self-closing tag; no children to process. | ||
} else if (this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
// Tag with children and a close-tag; process the children as args. | ||
this.tokens.removeToken(); | ||
this.processChildren(true); | ||
} else { | ||
this.tokens.appendCode(`, null`); | ||
throw new Error("Expected either /> or > at the end of the tag."); | ||
} | ||
return; | ||
} | ||
this.tokens.appendCode(`, {`); | ||
while (true) { | ||
if (this.tokens.matches2(_types.TokenType.jsxName, _types.TokenType.eq)) { | ||
this.processPropKeyName(); | ||
this.tokens.replaceToken(": "); | ||
if (this.tokens.matches1(_types.TokenType.braceL)) { | ||
this.tokens.replaceToken(""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
} else if (this.tokens.matches1(_types.TokenType.jsxTagStart)) { | ||
this.processJSXTag(); | ||
} else { | ||
this.processStringPropValue(); | ||
} | ||
} else if (this.tokens.matches1(_types.TokenType.jsxName)) { | ||
this.processPropKeyName(); | ||
this.tokens.appendCode(": true"); | ||
} else if (this.tokens.matches1(_types.TokenType.braceL)) { | ||
this.tokens.replaceToken(""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
// We're at the close-tag or the end of a self-closing tag, so remove | ||
// everything else and close the function call. | ||
this.tokens.removeInitialToken(); | ||
while (!this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
this.tokens.removeToken(); | ||
} | ||
this.tokens.replaceToken(")"); | ||
} | ||
/** | ||
* Get the code for the relevant function for this context: jsx, jsxs, | ||
* or jsxDEV. The following open-paren is included as well. | ||
* | ||
* These functions are only used for the automatic runtime, so they are always | ||
* auto-imported, but the auto-import will be either CJS or ESM based on the | ||
* target module format. | ||
*/ | ||
getJSXFuncInvocationCode(isStatic) { | ||
if (this.options.production) { | ||
if (isStatic) { | ||
return this.claimAutoImportedFuncInvocation("jsxs", "/jsx-runtime"); | ||
} else { | ||
break; | ||
return this.claimAutoImportedFuncInvocation("jsx", "/jsx-runtime"); | ||
} | ||
this.tokens.appendCode(","); | ||
} else { | ||
return this.claimAutoImportedFuncInvocation("jsxDEV", "/jsx-dev-runtime"); | ||
} | ||
if (devProps) { | ||
this.tokens.appendCode(` ${devProps}}`); | ||
} | ||
/** | ||
* Return the code to use for the createElement function, e.g. | ||
* `React.createElement`, including the following open-paren. | ||
* | ||
* This is the main function to use for the classic runtime. For the | ||
* automatic runtime, this function is used as a fallback function to | ||
* preserve behavior when there is a prop spread followed by an explicit | ||
* key. In that automatic runtime case, the function should be automatically | ||
* imported. | ||
*/ | ||
getCreateElementInvocationCode() { | ||
if (this.isAutomaticRuntime) { | ||
return this.claimAutoImportedFuncInvocation("createElement", ""); | ||
} else { | ||
this.tokens.appendCode("}"); | ||
const {jsxPragmaInfo} = this; | ||
const resolvedPragmaBaseName = this.importProcessor | ||
? this.importProcessor.getIdentifierReplacement(jsxPragmaInfo.base) || jsxPragmaInfo.base | ||
: jsxPragmaInfo.base; | ||
return `${resolvedPragmaBaseName}${jsxPragmaInfo.suffix}(`; | ||
} | ||
} | ||
processPropKeyName() { | ||
const keyName = this.tokens.identifierName(); | ||
if (keyName.includes("-")) { | ||
this.tokens.replaceToken(`'${keyName}'`); | ||
/** | ||
* Return the code to use as the component when compiling a shorthand | ||
* fragment, e.g. `React.Fragment`. | ||
* | ||
* This may be called from either the classic or automatic runtime, and | ||
* the value should be auto-imported for the automatic runtime. | ||
*/ | ||
getFragmentCode() { | ||
if (this.isAutomaticRuntime) { | ||
return this.claimAutoImportedName( | ||
"Fragment", | ||
this.options.production ? "/jsx-runtime" : "/jsx-dev-runtime", | ||
); | ||
} else { | ||
this.tokens.copyToken(); | ||
const {jsxPragmaInfo} = this; | ||
const resolvedFragmentPragmaBaseName = this.importProcessor | ||
? this.importProcessor.getIdentifierReplacement(jsxPragmaInfo.fragmentBase) || | ||
jsxPragmaInfo.fragmentBase | ||
: jsxPragmaInfo.fragmentBase; | ||
return resolvedFragmentPragmaBaseName + jsxPragmaInfo.fragmentSuffix; | ||
} | ||
} | ||
processStringPropValue() { | ||
const token = this.tokens.currentToken(); | ||
const valueCode = this.tokens.code.slice(token.start + 1, token.end - 1); | ||
const replacementCode = formatJSXTextReplacement(valueCode); | ||
const literalCode = formatJSXStringValueLiteral(valueCode); | ||
this.tokens.replaceToken(literalCode + replacementCode); | ||
/** | ||
* Return code that invokes the given function. | ||
* | ||
* When the imports transform is enabled, use the CJSImportTransformer | ||
* strategy of using `.call(void 0, ...` to avoid passing a `this` value in a | ||
* situation that would otherwise look like a method call. | ||
*/ | ||
claimAutoImportedFuncInvocation(funcName, importPathSuffix) { | ||
const funcCode = this.claimAutoImportedName(funcName, importPathSuffix); | ||
if (this.importProcessor) { | ||
return `${funcCode}.call(void 0, `; | ||
} else { | ||
return `${funcCode}(`; | ||
} | ||
} | ||
claimAutoImportedName(funcName, importPathSuffix) { | ||
if (this.importProcessor) { | ||
// CJS mode: claim a name for the module and mark it for import. | ||
const path = this.jsxImportSource + importPathSuffix; | ||
if (!this.cjsAutomaticModuleNameResolutions[path]) { | ||
this.cjsAutomaticModuleNameResolutions[path] = | ||
this.importProcessor.getFreeIdentifierForPath(path); | ||
} | ||
return `${this.cjsAutomaticModuleNameResolutions[path]}.${funcName}`; | ||
} else { | ||
// ESM mode: claim a name for this function and add it to the names that | ||
// should be auto-imported when the prefix is generated. | ||
if (!this.esmAutomaticImportNameResolutions[funcName]) { | ||
this.esmAutomaticImportNameResolutions[funcName] = this.nameManager.claimFreeName( | ||
`_${funcName}`, | ||
); | ||
} | ||
return this.esmAutomaticImportNameResolutions[funcName]; | ||
} | ||
} | ||
/** | ||
@@ -167,4 +356,147 @@ * Process the first part of a tag, before any props. | ||
processChildren() { | ||
/** | ||
* Starting at the beginning of the props, add the props argument to | ||
* React.createElement, including the comma before it. | ||
*/ | ||
processPropsObjectWithDevInfo(elementLocationCode) { | ||
const devProps = this.options.production | ||
? "" | ||
: `__self: this, __source: ${this.getDevSource(elementLocationCode)}`; | ||
if (!this.tokens.matches1(_types.TokenType.jsxName) && !this.tokens.matches1(_types.TokenType.braceL)) { | ||
if (devProps) { | ||
this.tokens.appendCode(`, {${devProps}}`); | ||
} else { | ||
this.tokens.appendCode(`, null`); | ||
} | ||
return; | ||
} | ||
this.tokens.appendCode(`, {`); | ||
this.processProps(false); | ||
if (devProps) { | ||
this.tokens.appendCode(` ${devProps}}`); | ||
} else { | ||
this.tokens.appendCode("}"); | ||
} | ||
} | ||
/** | ||
* Transform the core part of the props, assuming that a { has already been | ||
* inserted before us and that a } will be inserted after us. | ||
* | ||
* If extractKeyCode is true (i.e. when using any jsx... function), any prop | ||
* named "key" has its code captured and returned rather than being emitted to | ||
* the output code. This shifts line numbers, and emitting the code later will | ||
* correct line numbers again. If no key is found or if extractKeyCode is | ||
* false, this function returns null. | ||
*/ | ||
processProps(extractKeyCode) { | ||
let keyCode = null; | ||
while (true) { | ||
if (this.tokens.matches2(_types.TokenType.jsxName, _types.TokenType.eq)) { | ||
// This is a regular key={value} or key="value" prop. | ||
const propName = this.tokens.identifierName(); | ||
if (extractKeyCode && propName === "key") { | ||
if (keyCode !== null) { | ||
// The props list has multiple keys. Different implementations are | ||
// inconsistent about what to do here: as of this writing, Babel and | ||
// swc keep the *last* key and completely remove the rest, while | ||
// TypeScript uses the *first* key and leaves the others as regular | ||
// props. The React team collaborated with Babel on the | ||
// implementation of this behavior, so presumably the Babel behavior | ||
// is the one to use. | ||
// Since we won't ever be emitting the previous key code, we need to | ||
// at least emit its newlines here so that the line numbers match up | ||
// in the long run. | ||
this.tokens.appendCode(keyCode.replace(/[^\n]/g, "")); | ||
} | ||
// key | ||
this.tokens.removeToken(); | ||
// = | ||
this.tokens.removeToken(); | ||
const snapshot = this.tokens.snapshot(); | ||
this.processPropValue(); | ||
keyCode = this.tokens.dangerouslyGetAndRemoveCodeSinceSnapshot(snapshot); | ||
// Don't add a comma | ||
continue; | ||
} else { | ||
this.processPropName(propName); | ||
this.tokens.replaceToken(": "); | ||
this.processPropValue(); | ||
} | ||
} else if (this.tokens.matches1(_types.TokenType.jsxName)) { | ||
// This is a shorthand prop like <input disabled />. | ||
const propName = this.tokens.identifierName(); | ||
this.processPropName(propName); | ||
this.tokens.appendCode(": true"); | ||
} else if (this.tokens.matches1(_types.TokenType.braceL)) { | ||
// This is prop spread, like <div {...getProps()}>, which we can pass | ||
// through fairly directly as an object spread. | ||
this.tokens.replaceToken(""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
} else { | ||
break; | ||
} | ||
this.tokens.appendCode(","); | ||
} | ||
return keyCode; | ||
} | ||
processPropName(propName) { | ||
if (propName.includes("-")) { | ||
this.tokens.replaceToken(`'${propName}'`); | ||
} else { | ||
this.tokens.copyToken(); | ||
} | ||
} | ||
processPropValue() { | ||
if (this.tokens.matches1(_types.TokenType.braceL)) { | ||
this.tokens.replaceToken(""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
} else if (this.tokens.matches1(_types.TokenType.jsxTagStart)) { | ||
this.processJSXTag(); | ||
} else { | ||
this.processStringPropValue(); | ||
} | ||
} | ||
processStringPropValue() { | ||
const token = this.tokens.currentToken(); | ||
const valueCode = this.tokens.code.slice(token.start + 1, token.end - 1); | ||
const replacementCode = formatJSXTextReplacement(valueCode); | ||
const literalCode = formatJSXStringValueLiteral(valueCode); | ||
this.tokens.replaceToken(literalCode + replacementCode); | ||
} | ||
/** | ||
* Starting in the middle of the props object literal, produce an additional | ||
* prop for the children and close the object literal. | ||
*/ | ||
processAutomaticChildrenAndEndProps(jsxRole) { | ||
if (jsxRole === _tokenizer.JSXRole.StaticChildren) { | ||
this.tokens.appendCode(" children: ["); | ||
this.processChildren(false); | ||
this.tokens.appendCode("]}"); | ||
} else { | ||
// The parser information tells us whether we will see a real child or if | ||
// all remaining children (if any) will resolve to empty. If there are no | ||
// non-empty children, don't emit a children prop at all, but still | ||
// process children so that we properly transform the code into nothing. | ||
if (jsxRole === _tokenizer.JSXRole.OneChild) { | ||
this.tokens.appendCode(" children: "); | ||
} | ||
this.processChildren(false); | ||
this.tokens.appendCode("}"); | ||
} | ||
} | ||
/** | ||
* Transform children into a comma-separated list, which will be either | ||
* arguments to createElement or array elements of a children prop. | ||
*/ | ||
processChildren(needsInitialComma) { | ||
let needsComma = needsInitialComma; | ||
while (true) { | ||
if (this.tokens.matches2(_types.TokenType.jsxTagStart, _types.TokenType.slash)) { | ||
@@ -174,2 +506,3 @@ // Closing tag, so no more children. | ||
} | ||
let didEmitElement = false; | ||
if (this.tokens.matches1(_types.TokenType.braceL)) { | ||
@@ -183,19 +516,30 @@ if (this.tokens.matches2(_types.TokenType.braceL, _types.TokenType.braceR)) { | ||
// Interpolated expression. | ||
this.tokens.replaceToken(", "); | ||
this.tokens.replaceToken(needsComma ? ", " : ""); | ||
this.rootTransformer.processBalancedCode(); | ||
this.tokens.replaceToken(""); | ||
didEmitElement = true; | ||
} | ||
} else if (this.tokens.matches1(_types.TokenType.jsxTagStart)) { | ||
// Child JSX element | ||
this.tokens.appendCode(", "); | ||
this.tokens.appendCode(needsComma ? ", " : ""); | ||
this.processJSXTag(); | ||
} else if (this.tokens.matches1(_types.TokenType.jsxText)) { | ||
this.processChildTextElement(); | ||
didEmitElement = true; | ||
} else if (this.tokens.matches1(_types.TokenType.jsxText) || this.tokens.matches1(_types.TokenType.jsxEmptyText)) { | ||
didEmitElement = this.processChildTextElement(needsComma); | ||
} else { | ||
throw new Error("Unexpected token when processing JSX children."); | ||
} | ||
if (didEmitElement) { | ||
needsComma = true; | ||
} | ||
} | ||
} | ||
processChildTextElement() { | ||
/** | ||
* Turn a JSX text element into a string literal, or nothing at all if the JSX | ||
* text resolves to the empty string. | ||
* | ||
* Returns true if a string literal is emitted, false otherwise. | ||
*/ | ||
processChildTextElement(needsComma) { | ||
const token = this.tokens.currentToken(); | ||
@@ -207,52 +551,18 @@ const valueCode = this.tokens.code.slice(token.start, token.end); | ||
this.tokens.replaceToken(replacementCode); | ||
return false; | ||
} else { | ||
this.tokens.replaceToken(`, ${literalCode}${replacementCode}`); | ||
this.tokens.replaceToken(`${needsComma ? ", " : ""}${literalCode}${replacementCode}`); | ||
return true; | ||
} | ||
} | ||
processJSXTag() { | ||
const {jsxPragmaInfo} = this; | ||
const resolvedPragmaBaseName = this.importProcessor | ||
? this.importProcessor.getIdentifierReplacement(jsxPragmaInfo.base) || jsxPragmaInfo.base | ||
: jsxPragmaInfo.base; | ||
const firstTokenStart = this.tokens.currentToken().start; | ||
// First tag is always jsxTagStart. | ||
this.tokens.replaceToken(`${resolvedPragmaBaseName}${jsxPragmaInfo.suffix}(`); | ||
getDevSource(elementLocationCode) { | ||
return `{fileName: ${this.getFilenameVarName()}, ${elementLocationCode}}`; | ||
} | ||
if (this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
// Fragment syntax. | ||
const resolvedFragmentPragmaBaseName = this.importProcessor | ||
? this.importProcessor.getIdentifierReplacement(jsxPragmaInfo.fragmentBase) || | ||
jsxPragmaInfo.fragmentBase | ||
: jsxPragmaInfo.fragmentBase; | ||
this.tokens.replaceToken( | ||
`${resolvedFragmentPragmaBaseName}${jsxPragmaInfo.fragmentSuffix}, null`, | ||
); | ||
// Tag with children. | ||
this.processChildren(); | ||
while (!this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
this.tokens.replaceToken(""); | ||
} | ||
this.tokens.replaceToken(")"); | ||
} else { | ||
// Normal open tag or self-closing tag. | ||
this.processTagIntro(); | ||
this.processProps(firstTokenStart); | ||
if (this.tokens.matches2(_types.TokenType.slash, _types.TokenType.jsxTagEnd)) { | ||
// Self-closing tag. | ||
this.tokens.replaceToken(""); | ||
this.tokens.replaceToken(")"); | ||
} else if (this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
this.tokens.replaceToken(""); | ||
// Tag with children. | ||
this.processChildren(); | ||
while (!this.tokens.matches1(_types.TokenType.jsxTagEnd)) { | ||
this.tokens.replaceToken(""); | ||
} | ||
this.tokens.replaceToken(")"); | ||
} else { | ||
throw new Error("Expected either /> or > at the end of the tag."); | ||
} | ||
getFilenameVarName() { | ||
if (!this.filenameVarName) { | ||
this.filenameVarName = this.nameManager.claimFreeName("_jsxFileName"); | ||
} | ||
return this.filenameVarName; | ||
} | ||
@@ -259,0 +569,0 @@ } exports.default = JSXTransformer; |
@@ -33,3 +33,3 @@ import type { HelperManager } from "./HelperManager"; | ||
private generateImportReplacements; | ||
private getFreeIdentifierForPath; | ||
getFreeIdentifierForPath(path: string): string; | ||
private preprocessImportAtIndex; | ||
@@ -36,0 +36,0 @@ private preprocessExportAtIndex; |
@@ -10,42 +10,45 @@ export declare type Transform = "jsx" | "typescript" | "flow" | "imports" | "react-hot-loader" | "jest"; | ||
export interface Options { | ||
transforms: Array<Transform>; | ||
/** | ||
* If specified, function name to use in place of React.createClass when compiling JSX. | ||
* Unordered array of transform names describing both the allowed syntax | ||
* (where applicable) and the transformation behavior. | ||
*/ | ||
jsxPragma?: string; | ||
transforms: Array<Transform>; | ||
/** | ||
* If specified, function name to use in place of React.Fragment when compiling JSX. | ||
* Opts out of all ES syntax transformations: optional chaining, nullish | ||
* coalescing, class fields, numeric separators, optional catch binding. | ||
*/ | ||
jsxFragmentPragma?: string; | ||
disableESTransforms?: boolean; | ||
/** | ||
* If true, replicate the import behavior of TypeScript's esModuleInterop: false. | ||
* Transformation mode for the JSX transform. The automatic transform refers | ||
* to the transform behavior released with React 17, where the `jsx` function | ||
* (or a variation) is automatically imported. The classic transform refers to | ||
* the previous behavior using `React.createElement`. | ||
* | ||
* Default value: "classic" | ||
*/ | ||
enableLegacyTypeScriptModuleInterop?: boolean; | ||
jsxRuntime?: "classic" | "automatic"; | ||
/** | ||
* If true, replicate the import behavior Babel 5 and babel-plugin-add-module-exports. | ||
* Compile code for production use. Currently only applies to the JSX | ||
* transform. | ||
*/ | ||
enableLegacyBabel5ModuleInterop?: boolean; | ||
production?: boolean; | ||
/** | ||
* If specified, we also return a RawSourceMap object alongside the code. Currently, source maps | ||
* simply map each line to the original line without any mappings within lines, since Sucrase | ||
* preserves line numbers. filePath must be specified if this option is enabled. | ||
* If specified, import path prefix to use in place of "react" when compiling | ||
* JSX with the automatic runtime. | ||
*/ | ||
sourceMapOptions?: SourceMapOptions; | ||
jsxImportSource?: string; | ||
/** | ||
* File path to use in error messages, React display names, and source maps. | ||
* If specified, function name to use in place of React.createClass when | ||
* compiling JSX with the classic runtime. | ||
*/ | ||
filePath?: string; | ||
jsxPragma?: string; | ||
/** | ||
* If specified, omit any development-specific code in the output. | ||
* If specified, function name to use in place of React.Fragment when | ||
* compiling JSX with the classic runtime. | ||
*/ | ||
production?: boolean; | ||
jsxFragmentPragma?: string; | ||
/** | ||
* Opts out ES syntax transformations, like optional chaining, nullish coalescing, numeric | ||
* separators, etc. | ||
* If specified, the imports transform does not attempt to change dynamic | ||
* import() expressions into require() calls. | ||
*/ | ||
disableESTransforms?: boolean; | ||
/** | ||
* If specified, the imports transform does not attempt to change dynamic import() | ||
* expressions into require() calls. | ||
*/ | ||
preserveDynamicImport?: boolean; | ||
@@ -63,3 +66,22 @@ /** | ||
injectCreateRequireForImportRequire?: boolean; | ||
/** | ||
* If true, replicate the import behavior of TypeScript's esModuleInterop: false. | ||
*/ | ||
enableLegacyTypeScriptModuleInterop?: boolean; | ||
/** | ||
* If true, replicate the import behavior Babel 5 and babel-plugin-add-module-exports. | ||
*/ | ||
enableLegacyBabel5ModuleInterop?: boolean; | ||
/** | ||
* If specified, we also return a RawSourceMap object alongside the code. | ||
* Currently, source maps simply map each line to the original line without | ||
* any mappings within lines, since Sucrase preserves line numbers. filePath | ||
* must be specified if this option is enabled. | ||
*/ | ||
sourceMapOptions?: SourceMapOptions; | ||
/** | ||
* File path to use in error messages, React display names, and source maps. | ||
*/ | ||
filePath?: string; | ||
} | ||
export declare function validateOptions(options: Options): void; |
@@ -17,2 +17,12 @@ import { ContextualKeyword } from "./keywords"; | ||
} | ||
/** | ||
* Extra information on jsxTagStart tokens, used to determine which of the three | ||
* jsx functions are called in the automatic transform. | ||
*/ | ||
export declare enum JSXRole { | ||
NoChildren = 0, | ||
OneChild = 1, | ||
StaticChildren = 2, | ||
KeyAfterPropSpread = 3 | ||
} | ||
export declare function isDeclaration(token: Token): boolean; | ||
@@ -33,2 +43,3 @@ export declare function isNonTopLevelDeclaration(token: Token): boolean; | ||
identifierRole: IdentifierRole | null; | ||
jsxRole: JSXRole | null; | ||
shadowsGlobal: boolean; | ||
@@ -35,0 +46,0 @@ isAsyncOperation: boolean; |
@@ -68,59 +68,60 @@ /** | ||
jsxText = 56320, | ||
jsxTagStart = 57856, | ||
jsxTagEnd = 58368, | ||
typeParameterStart = 59904, | ||
nonNullAssertion = 60416, | ||
_break = 61456, | ||
_case = 62480, | ||
_catch = 63504, | ||
_continue = 64528, | ||
_debugger = 65552, | ||
_default = 66576, | ||
_do = 67600, | ||
_else = 68624, | ||
_finally = 69648, | ||
_for = 70672, | ||
_function = 72208, | ||
_if = 72720, | ||
_return = 73744, | ||
_switch = 74768, | ||
_throw = 76432, | ||
_try = 76816, | ||
_var = 77840, | ||
_let = 78864, | ||
_const = 79888, | ||
_while = 80912, | ||
_with = 81936, | ||
_new = 83472, | ||
_this = 84496, | ||
_super = 85520, | ||
_class = 86544, | ||
_extends = 87056, | ||
_export = 88080, | ||
_import = 89616, | ||
_yield = 90640, | ||
_null = 91664, | ||
_true = 92688, | ||
_false = 93712, | ||
_in = 94232, | ||
_instanceof = 95256, | ||
_typeof = 96912, | ||
_void = 97936, | ||
_delete = 98960, | ||
_async = 99856, | ||
_get = 100880, | ||
_set = 101904, | ||
_declare = 102928, | ||
_readonly = 103952, | ||
_abstract = 104976, | ||
_static = 106000, | ||
_public = 106512, | ||
_private = 107536, | ||
_protected = 108560, | ||
_override = 109584, | ||
_as = 111120, | ||
_enum = 112144, | ||
_type = 113168, | ||
_implements = 114192 | ||
jsxEmptyText = 57344, | ||
jsxTagStart = 58880, | ||
jsxTagEnd = 59392, | ||
typeParameterStart = 60928, | ||
nonNullAssertion = 61440, | ||
_break = 62480, | ||
_case = 63504, | ||
_catch = 64528, | ||
_continue = 65552, | ||
_debugger = 66576, | ||
_default = 67600, | ||
_do = 68624, | ||
_else = 69648, | ||
_finally = 70672, | ||
_for = 71696, | ||
_function = 73232, | ||
_if = 73744, | ||
_return = 74768, | ||
_switch = 75792, | ||
_throw = 77456, | ||
_try = 77840, | ||
_var = 78864, | ||
_let = 79888, | ||
_const = 80912, | ||
_while = 81936, | ||
_with = 82960, | ||
_new = 84496, | ||
_this = 85520, | ||
_super = 86544, | ||
_class = 87568, | ||
_extends = 88080, | ||
_export = 89104, | ||
_import = 90640, | ||
_yield = 91664, | ||
_null = 92688, | ||
_true = 93712, | ||
_false = 94736, | ||
_in = 95256, | ||
_instanceof = 96280, | ||
_typeof = 97936, | ||
_void = 98960, | ||
_delete = 99984, | ||
_async = 100880, | ||
_get = 101904, | ||
_set = 102928, | ||
_declare = 103952, | ||
_readonly = 104976, | ||
_abstract = 106000, | ||
_static = 107024, | ||
_public = 107536, | ||
_private = 108560, | ||
_protected = 109584, | ||
_override = 110608, | ||
_as = 112144, | ||
_enum = 113168, | ||
_type = 114192, | ||
_implements = 115216 | ||
} | ||
export declare function formatTokenType(tokenType: TokenType): string; |
export declare enum charCodes { | ||
backSpace = 8, | ||
lineFeed = 10, | ||
tab = 9, | ||
carriageReturn = 13, | ||
@@ -5,0 +6,0 @@ shiftOut = 14, |
@@ -23,3 +23,10 @@ import type { HelperManager } from "./HelperManager"; | ||
restoreToSnapshot(snapshot: TokenProcessorSnapshot): void; | ||
getResultCodeIndex(): number; | ||
/** | ||
* Remove and return the code generated since the snapshot, leaving the | ||
* current token position in-place. Unlike most TokenProcessor operations, | ||
* this operation can result in input/output line number mismatches because | ||
* the removed code may contain newlines, so this operation should be used | ||
* sparingly. | ||
*/ | ||
dangerouslyGetAndRemoveCodeSinceSnapshot(snapshot: TokenProcessorSnapshot): string; | ||
reset(): void; | ||
@@ -26,0 +33,0 @@ matchesContextualAtIndex(index: number, contextualKeyword: ContextualKeyword): boolean; |
import type CJSImportProcessor from "../CJSImportProcessor"; | ||
import type { Options } from "../index"; | ||
import type NameManager from "../NameManager"; | ||
import { JSXRole } from "../parser/tokenizer"; | ||
import type TokenProcessor from "../TokenProcessor"; | ||
@@ -14,25 +15,124 @@ import { JSXPragmaInfo } from "../util/getJSXPragmaInfo"; | ||
readonly options: Options; | ||
jsxPragmaInfo: JSXPragmaInfo; | ||
jsxImportSource: string; | ||
isAutomaticRuntime: boolean; | ||
lastLineNumber: number; | ||
lastIndex: number; | ||
filenameVarName: string | null; | ||
readonly jsxPragmaInfo: JSXPragmaInfo; | ||
esmAutomaticImportNameResolutions: { | ||
[name: string]: string; | ||
}; | ||
cjsAutomaticModuleNameResolutions: { | ||
[path: string]: string; | ||
}; | ||
constructor(rootTransformer: RootTransformer, tokens: TokenProcessor, importProcessor: CJSImportProcessor | null, nameManager: NameManager, options: Options); | ||
process(): boolean; | ||
getPrefixCode(): string; | ||
processJSXTag(): void; | ||
getElementLocationCode(firstTokenStart: number): string; | ||
/** | ||
* Lazily calculate line numbers to avoid unneeded work. We assume this is always called in | ||
* increasing order by index. | ||
* Get the line number for this source position. This is calculated lazily and | ||
* must be called in increasing order by index. | ||
*/ | ||
getLineNumberForIndex(index: number): number; | ||
getFilenameVarName(): string; | ||
processProps(firstTokenStart: number): void; | ||
processPropKeyName(): void; | ||
processStringPropValue(): void; | ||
/** | ||
* Convert the current JSX element to a call to jsx, jsxs, or jsxDEV. This is | ||
* the primary transformation for the automatic transform. | ||
* | ||
* Example: | ||
* <div a={1} key={2}>Hello{x}</div> | ||
* becomes | ||
* jsxs('div', {a: 1, children: ["Hello", x]}, 2) | ||
*/ | ||
transformTagToJSXFunc(elementLocationCode: string | null, jsxRole: JSXRole): void; | ||
/** | ||
* Convert the current JSX element to a createElement call. In the classic | ||
* runtime, this is the only case. In the automatic runtime, this is called | ||
* as a fallback in some situations. | ||
* | ||
* Example: | ||
* <div a={1} key={2}>Hello{x}</div> | ||
* becomes | ||
* React.createElement('div', {a: 1, key: 2}, "Hello", x) | ||
*/ | ||
transformTagToCreateElement(elementLocationCode: string | null): void; | ||
/** | ||
* Get the code for the relevant function for this context: jsx, jsxs, | ||
* or jsxDEV. The following open-paren is included as well. | ||
* | ||
* These functions are only used for the automatic runtime, so they are always | ||
* auto-imported, but the auto-import will be either CJS or ESM based on the | ||
* target module format. | ||
*/ | ||
getJSXFuncInvocationCode(isStatic: boolean): string; | ||
/** | ||
* Return the code to use for the createElement function, e.g. | ||
* `React.createElement`, including the following open-paren. | ||
* | ||
* This is the main function to use for the classic runtime. For the | ||
* automatic runtime, this function is used as a fallback function to | ||
* preserve behavior when there is a prop spread followed by an explicit | ||
* key. In that automatic runtime case, the function should be automatically | ||
* imported. | ||
*/ | ||
getCreateElementInvocationCode(): string; | ||
/** | ||
* Return the code to use as the component when compiling a shorthand | ||
* fragment, e.g. `React.Fragment`. | ||
* | ||
* This may be called from either the classic or automatic runtime, and | ||
* the value should be auto-imported for the automatic runtime. | ||
*/ | ||
getFragmentCode(): string; | ||
/** | ||
* Return code that invokes the given function. | ||
* | ||
* When the imports transform is enabled, use the CJSImportTransformer | ||
* strategy of using `.call(void 0, ...` to avoid passing a `this` value in a | ||
* situation that would otherwise look like a method call. | ||
*/ | ||
claimAutoImportedFuncInvocation(funcName: string, importPathSuffix: string): string; | ||
claimAutoImportedName(funcName: string, importPathSuffix: string): string; | ||
/** | ||
* Process the first part of a tag, before any props. | ||
*/ | ||
processTagIntro(): void; | ||
processChildren(): void; | ||
processChildTextElement(): void; | ||
processJSXTag(): void; | ||
/** | ||
* Starting at the beginning of the props, add the props argument to | ||
* React.createElement, including the comma before it. | ||
*/ | ||
processPropsObjectWithDevInfo(elementLocationCode: string | null): void; | ||
/** | ||
* Transform the core part of the props, assuming that a { has already been | ||
* inserted before us and that a } will be inserted after us. | ||
* | ||
* If extractKeyCode is true (i.e. when using any jsx... function), any prop | ||
* named "key" has its code captured and returned rather than being emitted to | ||
* the output code. This shifts line numbers, and emitting the code later will | ||
* correct line numbers again. If no key is found or if extractKeyCode is | ||
* false, this function returns null. | ||
*/ | ||
processProps(extractKeyCode: boolean): string | null; | ||
processPropName(propName: string): void; | ||
processPropValue(): void; | ||
processStringPropValue(): void; | ||
/** | ||
* Starting in the middle of the props object literal, produce an additional | ||
* prop for the children and close the object literal. | ||
*/ | ||
processAutomaticChildrenAndEndProps(jsxRole: JSXRole): void; | ||
/** | ||
* Transform children into a comma-separated list, which will be either | ||
* arguments to createElement or array elements of a children prop. | ||
*/ | ||
processChildren(needsInitialComma: boolean): void; | ||
/** | ||
* Turn a JSX text element into a string literal, or nothing at all if the JSX | ||
* text resolves to the empty string. | ||
* | ||
* Returns true if a string literal is emitted, false otherwise. | ||
*/ | ||
processChildTextElement(needsComma: boolean): boolean; | ||
getDevSource(elementLocationCode: string): string; | ||
getFilenameVarName(): string; | ||
} | ||
@@ -39,0 +139,0 @@ /** |
{ | ||
"name": "sucrase", | ||
"version": "3.25.0", | ||
"version": "3.26.0", | ||
"description": "Super-fast alternative to Babel for when you can target modern JS runtimes", | ||
@@ -66,3 +66,3 @@ "author": "Alan Pierce <alangpierce@gmail.com>", | ||
"prettier": "^2.6.2", | ||
"sucrase": "^3.24.0", | ||
"sucrase": "^3.25.0", | ||
"test262-harness": "^10.0.0", | ||
@@ -69,0 +69,0 @@ "ts-interface-builder": "^0.3.3", |
@@ -118,7 +118,17 @@ # Sucrase | ||
Like Babel, Sucrase compiles JSX to React functions by default, but can be | ||
configured for any JSX use case. | ||
By default, JSX is compiled to React functions in development mode. This can be | ||
configured with a few options: | ||
* **jsxPragma**: Element creation function, defaults to `React.createElement`. | ||
* **jsxFragmentPragma**: Fragment component, defaults to `React.Fragment`. | ||
* **jsxRuntime**: A string specifying the transform mode, which can be one of two values: | ||
* `"classic"` (default): The original JSX transform that calls `React.createElement` by default. | ||
To configure for non-React use cases, specify: | ||
* **jsxPragma**: Element creation function, defaults to `React.createElement`. | ||
* **jsxFragmentPragma**: Fragment component, defaults to `React.Fragment`. | ||
* `"automatic"`: The [new JSX transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) | ||
introduced with React 17, which calls `jsx` functions and auto-adds import statements. | ||
To configure for non-React use cases, specify: | ||
* **jsxImportSource**: Package name for auto-generated import statements, defaults to `react`. | ||
* **production**: If `true`, use production version of functions and don't include debugging | ||
information. When using React in production mode with the automatic transform, this *must* be | ||
set to true to avoid an error about `jsxDEV` being missing. | ||
@@ -125,0 +135,0 @@ ### Legacy CommonJS interop |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1082144
27311
276