diff --git a/browser/src/path.ts b/browser/src/path.ts index bc72123e1a8..65b1d4a1284 100644 --- a/browser/src/path.ts +++ b/browser/src/path.ts @@ -85,3 +85,7 @@ export function resolve(...paths: string[]): string { return resolvedParts.join('/'); } + +// Used for running the browser build locally in Vite +export const win32 = {}; +export const posix = {}; diff --git a/browser/src/wasm.ts b/browser/src/wasm.ts index c663b8ebd2c..afc48773f6d 100644 --- a/browser/src/wasm.ts +++ b/browser/src/wasm.ts @@ -5,7 +5,8 @@ import { parse } from '../../wasm/bindings_wasm.js'; export async function parseAsync( code: string, allowReturnOutsideFunction: boolean, + jsx: boolean, _signal?: AbortSignal | undefined | null ) { - return parse(code, allowReturnOutsideFunction); + return parse(code, allowReturnOutsideFunction, jsx); } diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index f9b8e1f00b3..a4ddaef6a55 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -7,6 +7,7 @@ import replaceBrowserModules from '../../build-plugins/replace-browser-modules'; import '../declarations.d'; import { examplesPlugin } from './create-examples'; import { renderMermaidGraphsPlugin } from './mermaid'; +import { replacePathPicomatch } from './replace-path-picomatch'; import { transposeTables } from './transpose-tables'; import { buildEnd, callback, transformPageData } from './verify-anchors'; @@ -157,7 +158,10 @@ export default defineConfig({ title: 'Rollup', transformPageData, vite: { + optimizeDeps: { exclude: ['@rollup/pluginutils'] }, plugins: [ + replacePathPicomatch(), + replaceBrowserModules(), renderMermaidGraphsPlugin(), replaceBrowserModules(), { diff --git a/docs/.vitepress/replace-path-picomatch.ts b/docs/.vitepress/replace-path-picomatch.ts new file mode 100644 index 00000000000..16cd7871ba7 --- /dev/null +++ b/docs/.vitepress/replace-path-picomatch.ts @@ -0,0 +1,22 @@ +import path from 'path'; +import type { Plugin } from 'vite'; + +export function replacePathPicomatch(): Plugin { + return { + enforce: 'pre', + load(id) { + if (id === 'picomatch') { + return 'export default {}'; + } + }, + name: 'replace-picomatch', + resolveId(source) { + if (source === 'picomatch') { + return { id: 'picomatch' }; + } + if (source === 'path') { + return path.resolve(__dirname, '../../browser/src/path.ts'); + } + } + }; +} diff --git a/docs/configuration-options/index.md b/docs/configuration-options/index.md index 01bd1d9d2bb..16adb70d3ba 100755 --- a/docs/configuration-options/index.md +++ b/docs/configuration-options/index.md @@ -12,7 +12,7 @@ title: Configuration Options | | | | --: | :-- | -| Type: | `(string \| RegExp)[]\| RegExp\| string\| (id: string, parentId: string, isResolved: boolean) => boolean` | +| Type: | `(string \| RegExp)[] \| RegExp \| string \| (id: string, parentId: string, isResolved: boolean) => boolean` | | CLI: | `-e`/`--external ` | Either a function that takes an `id` and returns `true` (external) or `false` (not external), or an `Array` of module IDs, or regular expressions to match module IDs, that should remain external to the bundle. Can also be just a single ID or regular expression. The matched IDs should be either: @@ -85,10 +85,10 @@ The conversion back to a relative import is done as if `output.file` or `output. ### input -| | | -| ----: | :------------------------------------------------------ | -| Type: | `string \| string []\| { [entryName: string]: string }` | -| CLI: | `-i`/`--input ` | +| | | +| ----: | :------------------------------------------------------- | +| Type: | `string \| string [] \| { [entryName: string]: string }` | +| CLI: | `-i`/`--input ` | The bundle's entry point(s) (e.g. your `main.js` or `app.js` or `index.js`). If you provide an array of entry points or an object mapping names to entry points, they will be bundled to separate output chunks. Unless the [`output.file`](#output-file) option is used, generated chunk names will follow the [`output.entryFileNames`](#output-entryfilenames) option. When using the object form, the `[name]` portion of the file name will be the name of the object property while for the array form, it will be the file name of the entry point. @@ -169,6 +169,182 @@ File names containing spaces can be specified by using quotes: rollup "main entry"="src/entry 1.js" "src/other entry.js" --format es ``` +### jsx + +| | | +| -------: | :--------------------------------- | +| Type: | `false \| JsxPreset \| JsxOptions` | +| CLI: | `--jsx `/`--no-jsx` | +| Default: | `false` | + +```typescript +type JsxPreset = 'react' | 'react-jsx' | 'preserve' | 'preserve-react'; + +type JsxOptions = + | { + mode: 'preserve'; + factory: string | null; + fragment: string | null; + importSource: string | null; + preset: JsxPreset | null; + } + | { + mode: 'classic'; + factory: string; + fragment: string; + importSource: string | null; + preset: JsxPreset | null; + } + | { + mode: 'automatic'; + factory: string; + importSource: string; + jsxImportSource: string; + preset: JsxPreset | null; + }; +``` + +Allows Rollup to process JSX syntax to either preserve or transform it depending on the [`jsx.mode`](#jsx-mode). If set to `false`, an error will be thrown if JSX syntax is encountered. You may also choose a preset that will set all options together: + +- `"react"`: For transpiling JSX to `React.createElement` calls, where `React` is the default import from `"react"`. This is similar to setting `"jsx": "react"` in TypeScript compiler options. + ```js + ({ + mode: 'classic', + factory: 'React.createElement', + fragment: 'React.Fragment', + importSource: 'react' + }); + ``` +- `"react-jsx"`: This will use the new optimized React transformation introduced with React 17 and is similar to setting `"jsx": "react-jsx"` in TypeScript compiler options. + ```js + ({ + mode: 'automatic', + factory: 'React.createElement', + importSource: 'react', + jsxImportSource: 'react/jsx-runtime' + }); + ``` +- `"preserve"`: This will preserve JSX in the output. This will still tree-shake unused JSX code and may rename JSX identifiers if there are conflicts in the output. + ```js + ({ + mode: 'preserve', + factory: null, + fragment: null, + importSource: null + }); + ``` +- `"preserve-react"`: This will preserve JSX in the output but ensure that the default export of `"react"` is in scope as a variable named `React`. + ```js + ({ + mode: 'preserve', + factory: 'React.createElement', + fragment: 'React.Fragment', + importSource: 'react' + }); + ``` + +#### jsx.mode + +| | | +| -------: | :--------------------------------------- | +| Type: | `"preserve" \| "classic" \| "automatic"` | +| CLI: | `--jsx.mode ` | +| Default: | `"classic"` | + +This will determine how JSX is processed: + +- `"preserve"`: Will keep JSX syntax in the output. +- `"classic"`: This will perform a JSX transformation as it is needed by older React versions or other frameworks like for instance [Preact](https://preactjs.com). As an example, here is how you would configure jsx for Preact: + + ```js + ({ + mode: 'classic', + factory: 'h', + fragment: 'Fragment', + importSource: 'preact' + }); + ``` + + This would perform the following transformation: + + ```jsx + // input + console.log(
hello
); + + // output + import { h } from 'preact'; + console.log(/*#__PURE__*/ h('div', null, 'hello')); + ``` + +- `"automatic"`: This will perform a JSX transformation using the [new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) introduced with React 17. In this mode, Rollup will try to import helpers from [`jsx.jsxImportSource`](#jsx-jsximportsource) to transform JSX. As there are certain edge cases, this mode may still fall back to using the classic transformations when [using the `key` property together with spread attributes](https://github.com/facebook/react/issues/20031#issuecomment-710346866). To this end, you can still specify `jsx.importSource`, `jsx.factory`, and `jsx.fragment` to configure classic mode. + +#### jsx.factory + +| | | +| -------: | :-------------------------------- | +| Type: | `string \| null` | +| CLI: | `--jsx.factory ` | +| Default: | `"React.createElement"` or `null` | + +The function Rollup uses to create JSX elements in `"classic"` mode or as a fallback in `"automatic"` mode. This is usually `React.createElement` for React or `h` for other frameworks. In `"preserve"` mode, this will ensure that the factory is in scope if [`jsx.importSource`](#jsx-importsource) is specified, or otherwise that a global variable of the same name would not be overridden by local variables. Only in `"preserve"` mode it is possible to set this value to `null`, in which case Rollup will not take care to keep any particular factory function in scope. + +If the value contains a `"."` like `React.createElement` and an `jsx.importSource` is specified, Rollup will assume that the left part, e.g. `React`, refers to the default export of the `jsx.importSource`. Otherwise, Rollup assumes it is a named export. + +#### jsx.fragment + +| | | +| -------: | :--------------------------- | +| Type: | `string \| null` | +| CLI: | `--jsx.fragment ` | +| Default: | `"React.Fragment"` or `null` | + +The element function Rollup uses to create JSX fragments. This is usually `React.Fragment` for React or `Fragment` for other frameworks. In `"preserve"` mode, this will ensure that the fragment is in scope if [`jsx.importSource`](#jsx-importsource) is specified, or otherwise that a global variable of the same name would not be overridden by local variables. Only in `"preserve"` mode it is possible to set this value to `null`, in which case Rollup will not take care to keep any particular fragment function in scope. + +If the value contains a `"."` like `React.Fragment` and an `jsx.importSource` is specified, Rollup will assume that the left part, e.g. `React`, refers to the default export of the `jsx.importSource`. Otherwise, Rollup assumes it is a named export. + +#### jsx.importSource + +| | | +| -------: | :----------------------------- | +| Type: | `string \| null` | +| CLI: | `--jsx.importSource ` | +| Default: | `null` | + +Where to import the element factory function and/or the fragment element from. If left to `null`, Rollup will assume that [`jsx.factory`](#jsx-factory) and [`jsx.fragment`](#jsx-fragment) refer to global variables and makes sure they are not shadowed by local variables. + +#### jsx.jsxImportSource + +| | | +| -------: | :-------------------------------- | +| Type: | `string` | +| CLI: | `--jsx.jsxImportSource ` | +| Default: | `"react/jsx-runtime"` | + +When using `"automatic"` mode, this will specify from where to import the `jsx`, `jsxs` and `Fragment` helpers needed for that transformation. It is not possible to get those from a global variable. + +#### jsx.preset + +| | | +| ----: | :--------------------- | +| Type: | JsxPreset | +| CLI: | `--jsx.preset ` | + +Allows choosing one of the presets listed above while overriding some options. + +```js twoslash +// ---cut-start--- +/** @type {import('rollup').RollupOptions} */ +// ---cut-end--- +export default { + jsx: { + preset: 'react', + importSource: 'preact', + factory: 'h' + } + // ... +}; +``` + ### output.dir | | | @@ -208,7 +384,7 @@ Specifies the format of the generated bundle. One of the following: | | | | --: | :-- | -| Type: | `{ [id: string]: string }\| ((id: string) => string)` | +| Type: | `{ [id: string]: string } \| ((id: string) => string)` | | CLI: | `-g`/`--globals ` | Specifies `id: variableName` pairs necessary for external imports in `umd`/`iife` bundles. For example, in a case like this… @@ -439,7 +615,7 @@ When using the CLI, errors will still be printed to the console as they are not | | | | --: | :-- | -| Type: | `boolean\| "ifRelativeSource"` | +| Type: | `boolean \| "ifRelativeSource"` | | CLI: | `--makeAbsoluteExternalsRelative`/`--no-makeAbsoluteExternalsRelative` | | Default: | `"ifRelativeSource"` | @@ -566,11 +742,11 @@ See [`onLog`](#onlog) for more information. ### output.assetFileNames -| | | -| -------: | :--------------------------------------------------- | -| Type: | `string\| ((assetInfo: PreRenderedAsset) => string)` | -| CLI: | `--assetFileNames ` | -| Default: | `"assets/[name]-[hash][extname]"` | +| | | +| -------: | :---------------------------------------------------- | +| Type: | `string \| ((assetInfo: PreRenderedAsset) => string)` | +| CLI: | `--assetFileNames ` | +| Default: | `"assets/[name]-[hash][extname]"` | ```typescript interface PreRenderedAsset { @@ -592,10 +768,10 @@ Forward slashes `/` can be used to place files in sub-directories. When using a ### output.banner/output.footer -| | | -| ----: | :--------------------------------------------------------------- | -| Type: | `string \| ((chunk: RenderedChunk) => string\| Promise)` | -| CLI: | `--banner`/`--footer ` | +| | | +| --: | :-- | +| Type: | `string \| ((chunk: RenderedChunk) => string \| Promise)` | +| CLI: | `--banner`/`--footer ` | See the [`renderChunk`](../plugin-development/index.md#renderchunk) hook for the `RenderedChunk` type. @@ -758,7 +934,7 @@ Whether to add import attributes to external imports in the output if the output | | | | --: | :-- | -| Type: | `"es5" \| "es2015"\| { arrowFunctions?: boolean, constBindings?: boolean, objectShorthand?: boolean, preset?: "es5"\| "es2015", reservedNamesAsProps?: boolean, symbols?: boolean }` | +| Type: | `"es5" \| "es2015" \| { arrowFunctions?: boolean, constBindings?: boolean, objectShorthand?: boolean, preset?: "es5" \| "es2015", reservedNamesAsProps?: boolean, symbols?: boolean }` | | CLI: | `--generatedCode ` | | Default: | `"es5"` | @@ -980,7 +1156,7 @@ This will inline dynamic imports instead of creating new chunks to create a sing | | | | --: | :-- | -| Type: | `"compat" \| "auto"\| "esModule"\| "default"\| "defaultOnly"\| ((id: string) => "compat"\| "auto"\| "esModule"\| "default"\| "defaultOnly")` | +| Type: | `"compat" \| "auto" \| "esModule" \| "default" \| "defaultOnly" \| ((id: string) => "compat" \| "auto" \| "esModule" \| "default" \| "defaultOnly")` | | CLI: | `--interop ` | | Default: | `"default"` | @@ -1239,10 +1415,10 @@ There are some additional options that have an effect on the generated interop c ### output.intro/output.outro -| | | -| ----: | :--------------------------------------------------------------- | -| Type: | `string \| ((chunk: RenderedChunk) => string\| Promise)` | -| CLI: | `--intro`/`--outro ` | +| | | +| --: | :-- | +| Type: | `string \| ((chunk: RenderedChunk) => string \| Promise)` | +| CLI: | `--intro`/`--outro ` | Similar to [`output.banner/output.footer`](#output-banner-output-footer), except that the code goes _inside_ any format-specific wrapper. @@ -1553,7 +1729,7 @@ This option is particularly useful while using plugins such as `@rollup/plugin-n | | | | -------: | :---------------------------------- | -| Type: | `boolean \| 'inline'\| 'hidden'` | +| Type: | `boolean \| 'inline' \| 'hidden'` | | CLI: | `-m`/`--sourcemap`/`--no-sourcemap` | | Default: | `false` | @@ -1696,7 +1872,7 @@ This option specifies the directory name for "virtual" files that might be emitt | | | | --: | :-- | -| Type: | `"strict" \| "allow-extension" \| "exports-only"\| false` | +| Type: | `"strict" \| "allow-extension" \| "exports-only" \| false` | | CLI: | `--preserveEntrySignatures `/`--no-preserveEntrySignatures` | | Default: | `"exports-only"` | @@ -1975,11 +2151,11 @@ See also [`output.interop`](#output-interop). ### output.exports -| | | -| -------: | :--------------------------------------- | -| Type: | `"auto" \| "default"\| "named"\| "none"` | -| CLI: | `--exports ` | -| Default: | `'auto'` | +| | | +| -------: | :----------------------------------------- | +| Type: | `"auto" \| "default" \| "named" \| "none"` | +| CLI: | `--exports ` | +| Default: | `'auto'` | What export mode to use. Defaults to `auto`, which guesses your intentions based on what the `input` module exports: @@ -2232,7 +2408,7 @@ If this option is provided, bundling will not fail if bindings are imported from | | | | -------: | :--------------------------------------------------- | | Type: | `boolean \| TreeshakingPreset \| TreeshakingOptions` | -| CLI: | `--treeshake`/`--no-treeshake` | +| CLI: | `--treeshake `/`--no-treeshake` | | Default: | `true` | ```typescript @@ -2398,7 +2574,7 @@ styled().div(); // removed | | | | --: | :-- | -| Type: | `boolean\| "no-external"\| string[]\| (id: string, external: boolean) => boolean` | +| Type: | `boolean \| "no-external" \| string[] \| (id: string, external: boolean) => boolean` | | CLI: | `--treeshake.moduleSideEffects`/`--no-treeshake.moduleSideEffects`/`--treeshake.moduleSideEffects no-external` | | Default: | `true` | @@ -2486,10 +2662,10 @@ Note that despite the name, this option does not "add" side effects to modules t #### treeshake.preset -| | | -| ----: | :--------------------------------------- | -| Type: | `"smallest" \| "safest"\| "recommended"` | -| CLI: | `--treeshake `
| +| | | +| ----: | :---------------------------------------- | +| Type: | `"smallest" \| "safest" \| "recommended"` | +| CLI: | `--treeshake `
| Allows choosing one of the presets listed above while overriding some options. @@ -2510,7 +2686,7 @@ export default { | | | | --: | :-- | -| Type: | `boolean\| 'always'` | +| Type: | `boolean \| 'always'` | | CLI: | `--treeshake.propertyReadSideEffects`/`--no-treeshake.propertyReadSideEffects` | | Default: | `true` | @@ -2733,10 +2909,10 @@ Whether to clear the screen when a rebuild is triggered. ### watch.exclude -| | | -| ----: | :--------------------------------------- | -| Type: | `string \| RegExp\| (string\| RegExp)[]` | -| CLI: | `--watch.exclude ` | +| | | +| ----: | :----------------------------------------- | +| Type: | `string \| RegExp \| (string \| RegExp)[]` | +| CLI: | `--watch.exclude ` | Prevent files from being watched: @@ -2755,10 +2931,10 @@ export default { ### watch.include -| | | -| ----: | :--------------------------------------- | -| Type: | `string \| RegExp\| (string\| RegExp)[]` | -| CLI: | `--watch.include ` | +| | | +| ----: | :----------------------------------------- | +| Type: | `string \| RegExp \| (string \| RegExp)[]` | +| CLI: | `--watch.include ` | Limit the file-watching to certain files. Note that this only filters the module graph but does not allow adding additional watch files: diff --git a/docs/repl/examples/00/example.json b/docs/repl/examples/00/example.json index 552e7b13123..64ee77b4684 100644 --- a/docs/repl/examples/00/example.json +++ b/docs/repl/examples/00/example.json @@ -1,6 +1,9 @@ { - "title": "Tree-shaking", + "title": "Named exports", "options": { - "treeshake": true + "output": { + "exports": "auto", + "esModule": "if-default-prop" + } } } diff --git a/docs/repl/examples/00/modules/main.js b/docs/repl/examples/00/modules/main.js index 140917331c5..a88f397e682 100644 --- a/docs/repl/examples/00/modules/main.js +++ b/docs/repl/examples/00/modules/main.js @@ -1,4 +1,17 @@ -// TREE-SHAKING -import { cube } from './maths.js'; +// NAMED EXPORTS +// There are many ways to export bindings +// from an ES2015 module +export var foo = 1; -console.log(cube(5)); // 125 +export function bar() { + // try changing this to `foo++` + // when generating CommonJS + return foo; +} + +function baz() { + return bar(); +} + +export * from './qux'; +export { baz }; diff --git a/docs/repl/examples/01/modules/qux.js b/docs/repl/examples/00/modules/qux.js similarity index 100% rename from docs/repl/examples/01/modules/qux.js rename to docs/repl/examples/00/modules/qux.js diff --git a/docs/repl/examples/01/example.json b/docs/repl/examples/01/example.json index 64ee77b4684..552e7b13123 100644 --- a/docs/repl/examples/01/example.json +++ b/docs/repl/examples/01/example.json @@ -1,9 +1,6 @@ { - "title": "Named exports", + "title": "Tree-shaking", "options": { - "output": { - "exports": "auto", - "esModule": "if-default-prop" - } + "treeshake": true } } diff --git a/docs/repl/examples/01/modules/main.js b/docs/repl/examples/01/modules/main.js index a88f397e682..140917331c5 100644 --- a/docs/repl/examples/01/modules/main.js +++ b/docs/repl/examples/01/modules/main.js @@ -1,17 +1,4 @@ -// NAMED EXPORTS -// There are many ways to export bindings -// from an ES2015 module -export var foo = 1; +// TREE-SHAKING +import { cube } from './maths.js'; -export function bar() { - // try changing this to `foo++` - // when generating CommonJS - return foo; -} - -function baz() { - return bar(); -} - -export * from './qux'; -export { baz }; +console.log(cube(5)); // 125 diff --git a/docs/repl/examples/00/modules/maths.js b/docs/repl/examples/01/modules/maths.js similarity index 100% rename from docs/repl/examples/00/modules/maths.js rename to docs/repl/examples/01/modules/maths.js diff --git a/docs/repl/examples/07/example.json b/docs/repl/examples/07/example.json index 7dd6f889f9d..2e559572a75 100644 --- a/docs/repl/examples/07/example.json +++ b/docs/repl/examples/07/example.json @@ -1,10 +1,10 @@ { "title": "Multiple Entry Modules", - "entryModules": ["main.js", "otherEntry.js"], - "options": { - "output": { - "minifyInternalExports": false, - "preserveModules": false - } - } + "entryModules": ["main.js", "otherEntry.js"], + "options": { + "output": { + "minifyInternalExports": false, + "preserveModules": false + } + } } diff --git a/docs/repl/examples/08/example.json b/docs/repl/examples/08/example.json new file mode 100644 index 00000000000..f085d76c5cd --- /dev/null +++ b/docs/repl/examples/08/example.json @@ -0,0 +1,8 @@ +{ + "title": "JSX", + "options": { + "jsx": { + "mode": "preserve" + } + } +} diff --git a/docs/repl/examples/08/modules/main.js b/docs/repl/examples/08/modules/main.js new file mode 100644 index 00000000000..e9744cbb67b --- /dev/null +++ b/docs/repl/examples/08/modules/main.js @@ -0,0 +1,6 @@ +// JSX SUPPORT +// Try different jsx.mode and see how it is transformed +import './other.js'; +const Foo = ({ world }) =>
Hello {world}!
; + +console.log(); diff --git a/docs/repl/examples/08/modules/other.js b/docs/repl/examples/08/modules/other.js new file mode 100644 index 00000000000..724d561c110 --- /dev/null +++ b/docs/repl/examples/08/modules/other.js @@ -0,0 +1,2 @@ +const Foo = () =>
This is deconflicted!
; +console.log(); diff --git a/docs/repl/stores/options.ts b/docs/repl/stores/options.ts index 0f1fd0371a4..b8207c94e7f 100644 --- a/docs/repl/stores/options.ts +++ b/docs/repl/stores/options.ts @@ -114,6 +114,7 @@ export const useOptions = defineStore('options2', () => { return value != null && interopFormats.has(value); }); const externalImports = computed(() => rollupOutputStore.output?.externalImports || []); + const isJsxEnabled = computed(() => optionJsx.value.value === true); const isTreeshakeEnabled = computed(() => [undefined, true].includes(optionTreeshake.value.value as any) ); @@ -375,6 +376,39 @@ export const useOptions = defineStore('options2', () => { defaultValue: false, name: 'shimMissingExports' }); + const optionJsx = getSelect({ + defaultValue: false, + name: 'jsx', + options: () => [false, true, 'preserve', 'preserve-react', 'react', 'react-jsx'] + }); + const optionJsxFactory = getString({ + available: isJsxEnabled, + name: 'jsx.factory' + }); + const optionJsxFragment = getString({ + available: computed(() => isJsxEnabled.value && optionJsxMode.value.value !== 'automatic'), + name: 'jsx.fragment' + }); + const optionJsxImportSource = getString({ + available: isJsxEnabled, + name: 'jsx.importSource' + }); + const optionJsxJsxImportSource = getString({ + available: computed(() => isJsxEnabled.value && optionJsxMode.value.value === 'automatic'), + name: 'jsx.jsxImportSource' + }); + const optionJsxMode = getSelect({ + available: isJsxEnabled, + defaultValue: 'classic', + name: 'jsx.mode', + options: () => ['classic', 'automatic', 'preserve'] + }); + const optionJsxPreset = getSelect({ + available: isJsxEnabled, + defaultValue: null, + name: 'jsx.preset', + options: () => [null, 'preserve', 'preserve-react', 'react', 'react-jsx'] + }); const optionTreeshake = getSelect({ defaultValue: true, name: 'treeshake', @@ -422,6 +456,13 @@ export const useOptions = defineStore('options2', () => { const optionList: OptionType[] = [ optionContext, optionExperimentalLogSideEffects, + optionJsx, + optionJsxMode, + optionJsxFactory, + optionJsxFragment, + optionJsxImportSource, + optionJsxJsxImportSource, + optionJsxPreset, optionOutputAmdAutoId, optionOutputAmdBasePath, optionOutputAmdDefine, @@ -511,7 +552,8 @@ export const useOptions = defineStore('options2', () => { while ((key = path.shift())) { subOptions = subOptions?.[key]; } - value.value = name === 'treeshake' && typeof subOptions === 'object' ? true : subOptions; + value.value = + ['jsx', 'treeshake'].includes(name) && typeof subOptions === 'object' ? true : subOptions; } } }; @@ -652,7 +694,7 @@ function getOptionsObject(options: Ref): Ref { let key: string | undefined; let subOptions: any = object; while ((key = path.shift())) { - // Special logic to handle treeshake option + // Special logic to handle jsx/treeshake option if (subOptions[key] === true) { subOptions[key] = {}; } diff --git a/docs/repl/stores/rollup.ts b/docs/repl/stores/rollup.ts index e79140a329d..fb2d84df2d7 100644 --- a/docs/repl/stores/rollup.ts +++ b/docs/repl/stores/rollup.ts @@ -117,6 +117,7 @@ export const useRollup = defineStore('rollup', () => { instance }; } catch (error) { + console.error(error); loaded.value = { error: error as Error, instance: null diff --git a/native.d.ts b/native.d.ts index 0085313ac22..0047a3ee494 100644 --- a/native.d.ts +++ b/native.d.ts @@ -3,8 +3,8 @@ /* auto-generated by NAPI-RS */ -export declare function parse(code: string, allowReturnOutsideFunction: boolean): Buffer -export declare function parseAsync(code: string, allowReturnOutsideFunction: boolean, signal?: AbortSignal | undefined | null): Promise +export declare function parse(code: string, allowReturnOutsideFunction: boolean, jsx: boolean): Buffer +export declare function parseAsync(code: string, allowReturnOutsideFunction: boolean, jsx: boolean, signal?: AbortSignal | undefined | null): Promise export declare function xxhashBase64Url(input: Uint8Array): string export declare function xxhashBase36(input: Uint8Array): string export declare function xxhashBase16(input: Uint8Array): string diff --git a/package-lock.json b/package-lock.json index 7ce0abdcfc6..7cec3ec7b33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "@vue/language-server": "^2.1.6", "acorn": "^8.12.1", "acorn-import-assertions": "^1.9.0", + "acorn-jsx": "^5.3.2", "buble": "^0.20.0", "builtin-modules": "^4.0.0", "chokidar": "^3.6.0", @@ -12103,36 +12104,6 @@ "rollup": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" } }, - "node_modules/rollup-plugin-license/node_modules/fdir": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.3.0.tgz", - "integrity": "sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/rollup-plugin-license/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/rollup-plugin-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/rollup-plugin-string/-/rollup-plugin-string-3.0.0.tgz", diff --git a/package.json b/package.json index 19b248575ac..6ccb1c22480 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "@vue/language-server": "^2.1.6", "acorn": "^8.12.1", "acorn-import-assertions": "^1.9.0", + "acorn-jsx": "^5.3.2", "buble": "^0.20.0", "builtin-modules": "^4.0.0", "chokidar": "^3.6.0", diff --git a/rust/bindings_napi/src/lib.rs b/rust/bindings_napi/src/lib.rs index 219334231ab..92e4cf9c1ba 100644 --- a/rust/bindings_napi/src/lib.rs +++ b/rust/bindings_napi/src/lib.rs @@ -12,6 +12,7 @@ static ALLOC: mimalloc_rust::GlobalMiMalloc = mimalloc_rust::GlobalMiMalloc; pub struct ParseTask { pub code: String, pub allow_return_outside_function: bool, + pub jsx: bool, } #[napi] @@ -20,7 +21,14 @@ impl Task for ParseTask { type JsValue = Buffer; fn compute(&mut self) -> Result { - Ok(parse_ast(self.code.clone(), self.allow_return_outside_function).into()) + Ok( + parse_ast( + self.code.clone(), + self.allow_return_outside_function, + self.jsx, + ) + .into(), + ) } fn resolve(&mut self, _env: Env, output: Self::Output) -> Result { @@ -29,20 +37,22 @@ impl Task for ParseTask { } #[napi] -pub fn parse(code: String, allow_return_outside_function: bool) -> Buffer { - parse_ast(code, allow_return_outside_function).into() +pub fn parse(code: String, allow_return_outside_function: bool, jsx: bool) -> Buffer { + parse_ast(code, allow_return_outside_function, jsx).into() } #[napi] pub fn parse_async( code: String, allow_return_outside_function: bool, + jsx: bool, signal: Option, ) -> AsyncTask { AsyncTask::with_optional_signal( ParseTask { code, allow_return_outside_function, + jsx, }, signal, ) diff --git a/rust/bindings_wasm/src/lib.rs b/rust/bindings_wasm/src/lib.rs index 23e0f0d654a..197809bae52 100644 --- a/rust/bindings_wasm/src/lib.rs +++ b/rust/bindings_wasm/src/lib.rs @@ -3,8 +3,8 @@ use parse_ast::parse_ast; use wasm_bindgen::prelude::*; #[wasm_bindgen] -pub fn parse(code: String, allow_return_outside_function: bool) -> Vec { - parse_ast(code, allow_return_outside_function) +pub fn parse(code: String, allow_return_outside_function: bool, jsx: bool) -> Vec { + parse_ast(code, allow_return_outside_function, jsx) } #[wasm_bindgen(js_name=xxhashBase64Url)] diff --git a/rust/parse_ast/src/ast_nodes/array_expression.rs b/rust/parse_ast/src/ast_nodes/array_expression.rs index ea999b5d43f..57bfe1f7992 100644 --- a/rust/parse_ast/src/ast_nodes/array_expression.rs +++ b/rust/parse_ast/src/ast_nodes/array_expression.rs @@ -6,7 +6,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_array_expression(&mut self, array_literal: &ArrayLit) { + pub(crate) fn store_array_expression(&mut self, array_literal: &ArrayLit) { let end_position = self.add_type_and_start( &TYPE_ARRAY_EXPRESSION, &array_literal.span, diff --git a/rust/parse_ast/src/ast_nodes/array_pattern.rs b/rust/parse_ast/src/ast_nodes/array_pattern.rs index 90997dda110..913be200715 100644 --- a/rust/parse_ast/src/ast_nodes/array_pattern.rs +++ b/rust/parse_ast/src/ast_nodes/array_pattern.rs @@ -6,7 +6,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_array_pattern(&mut self, array_pattern: &ArrayPat) { + pub(crate) fn store_array_pattern(&mut self, array_pattern: &ArrayPat) { let end_position = self.add_type_and_start( &TYPE_ARRAY_PATTERN, &array_pattern.span, diff --git a/rust/parse_ast/src/ast_nodes/arrow_function_expression.rs b/rust/parse_ast/src/ast_nodes/arrow_function_expression.rs index c737e458045..44fd2d76c9b 100644 --- a/rust/parse_ast/src/ast_nodes/arrow_function_expression.rs +++ b/rust/parse_ast/src/ast_nodes/arrow_function_expression.rs @@ -10,7 +10,7 @@ use crate::convert_ast::converter::{convert_annotation, AstConverter}; use crate::store_arrow_function_expression_flags; impl<'a> AstConverter<'a> { - pub fn store_arrow_function_expression(&mut self, arrow_expression: &ArrowExpr) { + pub(crate) fn store_arrow_function_expression(&mut self, arrow_expression: &ArrowExpr) { let end_position = self.add_type_and_start( &TYPE_ARROW_FUNCTION_EXPRESSION, &arrow_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/assignment_expression.rs b/rust/parse_ast/src/ast_nodes/assignment_expression.rs index 1b159de3e7a..4a696977db9 100644 --- a/rust/parse_ast/src/ast_nodes/assignment_expression.rs +++ b/rust/parse_ast/src/ast_nodes/assignment_expression.rs @@ -10,7 +10,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_assignment_expression; impl<'a> AstConverter<'a> { - pub fn store_assignment_expression(&mut self, assignment_expression: &AssignExpr) { + pub(crate) fn store_assignment_expression(&mut self, assignment_expression: &AssignExpr) { store_assignment_expression!( self, span => assignment_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/assignment_pattern.rs b/rust/parse_ast/src/ast_nodes/assignment_pattern.rs index 05a0c8a26ec..3e26ccfaa43 100644 --- a/rust/parse_ast/src/ast_nodes/assignment_pattern.rs +++ b/rust/parse_ast/src/ast_nodes/assignment_pattern.rs @@ -8,7 +8,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_assignment_pattern_and_get_left_position( + pub(crate) fn store_assignment_pattern_and_get_left_position( &mut self, span: &Span, left: PatternOrIdentifier, @@ -37,7 +37,7 @@ impl<'a> AstConverter<'a> { left_position } - pub fn convert_assignment_pattern(&mut self, assignment_pattern: &AssignPat) { + pub(crate) fn convert_assignment_pattern(&mut self, assignment_pattern: &AssignPat) { self.store_assignment_pattern_and_get_left_position( &assignment_pattern.span, PatternOrIdentifier::Pattern(&assignment_pattern.left), @@ -46,7 +46,7 @@ impl<'a> AstConverter<'a> { } } -pub enum PatternOrIdentifier<'a> { +pub(crate) enum PatternOrIdentifier<'a> { Pattern(&'a Pat), Identifier(&'a Ident), } diff --git a/rust/parse_ast/src/ast_nodes/await_expression.rs b/rust/parse_ast/src/ast_nodes/await_expression.rs index 820a7c9d1c8..950ec79f210 100644 --- a/rust/parse_ast/src/ast_nodes/await_expression.rs +++ b/rust/parse_ast/src/ast_nodes/await_expression.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_await_expression; impl<'a> AstConverter<'a> { - pub fn store_await_expression(&mut self, await_expression: &AwaitExpr) { + pub(crate) fn store_await_expression(&mut self, await_expression: &AwaitExpr) { store_await_expression!( self, span => await_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/binary_expression.rs b/rust/parse_ast/src/ast_nodes/binary_expression.rs index 9cd97046d41..e4e17702be9 100644 --- a/rust/parse_ast/src/ast_nodes/binary_expression.rs +++ b/rust/parse_ast/src/ast_nodes/binary_expression.rs @@ -14,7 +14,7 @@ use crate::convert_ast::converter::string_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_binary_expression(&mut self, binary_expression: &BinExpr) { + pub(crate) fn store_binary_expression(&mut self, binary_expression: &BinExpr) { let end_position = self.add_type_and_start( match binary_expression.op { BinaryOp::LogicalOr | BinaryOp::LogicalAnd | BinaryOp::NullishCoalescing => { diff --git a/rust/parse_ast/src/ast_nodes/block_statement.rs b/rust/parse_ast/src/ast_nodes/block_statement.rs index fec1816c0fc..dcee9b291da 100644 --- a/rust/parse_ast/src/ast_nodes/block_statement.rs +++ b/rust/parse_ast/src/ast_nodes/block_statement.rs @@ -6,7 +6,11 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_block_statement(&mut self, block_statement: &BlockStmt, check_directive: bool) { + pub(crate) fn store_block_statement( + &mut self, + block_statement: &BlockStmt, + check_directive: bool, + ) { let end_position = self.add_type_and_start( &TYPE_BLOCK_STATEMENT, &block_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/break_statement.rs b/rust/parse_ast/src/ast_nodes/break_statement.rs index ef54b723d1a..c92c4133fa7 100644 --- a/rust/parse_ast/src/ast_nodes/break_statement.rs +++ b/rust/parse_ast/src/ast_nodes/break_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_break_statement; impl<'a> AstConverter<'a> { - pub fn store_break_statement(&mut self, break_statement: &BreakStmt) { + pub(crate) fn store_break_statement(&mut self, break_statement: &BreakStmt) { store_break_statement!( self, span => break_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/call_expression.rs b/rust/parse_ast/src/ast_nodes/call_expression.rs index e8d3c526575..e5dff9ea97a 100644 --- a/rust/parse_ast/src/ast_nodes/call_expression.rs +++ b/rust/parse_ast/src/ast_nodes/call_expression.rs @@ -10,7 +10,7 @@ use crate::convert_ast::converter::{convert_annotation, AstConverter}; use crate::store_call_expression_flags; impl<'a> AstConverter<'a> { - pub fn store_call_expression( + pub(crate) fn store_call_expression( &mut self, span: &Span, is_optional: bool, @@ -70,7 +70,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, span); } - pub fn convert_optional_call( + pub(crate) fn convert_optional_call( &mut self, optional_call: &OptCall, is_optional: bool, @@ -86,7 +86,7 @@ impl<'a> AstConverter<'a> { } } -pub enum StoredCallee<'a> { +pub(crate) enum StoredCallee<'a> { Expression(&'a Expr), Super(&'a Super), } diff --git a/rust/parse_ast/src/ast_nodes/catch_clause.rs b/rust/parse_ast/src/ast_nodes/catch_clause.rs index 78e85906e15..90323376569 100644 --- a/rust/parse_ast/src/ast_nodes/catch_clause.rs +++ b/rust/parse_ast/src/ast_nodes/catch_clause.rs @@ -7,7 +7,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_catch_clause(&mut self, catch_clause: &CatchClause) { + pub(crate) fn store_catch_clause(&mut self, catch_clause: &CatchClause) { let end_position = self.add_type_and_start( &TYPE_CATCH_CLAUSE, &catch_clause.span, diff --git a/rust/parse_ast/src/ast_nodes/chain_expression.rs b/rust/parse_ast/src/ast_nodes/chain_expression.rs index 4d29a797f69..f034d8cd5ea 100644 --- a/rust/parse_ast/src/ast_nodes/chain_expression.rs +++ b/rust/parse_ast/src/ast_nodes/chain_expression.rs @@ -6,7 +6,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_chain_expression( + pub(crate) fn store_chain_expression( &mut self, optional_chain_expression: &OptChainExpr, is_chained: bool, diff --git a/rust/parse_ast/src/ast_nodes/class_body.rs b/rust/parse_ast/src/ast_nodes/class_body.rs index 7d187e60441..696ce2434dc 100644 --- a/rust/parse_ast/src/ast_nodes/class_body.rs +++ b/rust/parse_ast/src/ast_nodes/class_body.rs @@ -6,7 +6,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_class_body(&mut self, class_members: &[ClassMember], start: u32, end: u32) { + pub(crate) fn store_class_body(&mut self, class_members: &[ClassMember], start: u32, end: u32) { let end_position = self.add_type_and_explicit_start(&TYPE_CLASS_BODY, start, CLASS_BODY_RESERVED_BYTES); let class_members_filtered: Vec<&ClassMember> = class_members diff --git a/rust/parse_ast/src/ast_nodes/class_declaration.rs b/rust/parse_ast/src/ast_nodes/class_declaration.rs index 73cdd733e0f..7b79633f8fe 100644 --- a/rust/parse_ast/src/ast_nodes/class_declaration.rs +++ b/rust/parse_ast/src/ast_nodes/class_declaration.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::ast_constants::TYPE_CLASS_DECLARATION; use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_class_declaration(&mut self, class_declaration: &ClassDecl) { + pub(crate) fn store_class_declaration(&mut self, class_declaration: &ClassDecl) { self.store_class_node( &TYPE_CLASS_DECLARATION, Some(&class_declaration.ident), diff --git a/rust/parse_ast/src/ast_nodes/class_expression.rs b/rust/parse_ast/src/ast_nodes/class_expression.rs index c5515fe1ef5..dea6030d841 100644 --- a/rust/parse_ast/src/ast_nodes/class_expression.rs +++ b/rust/parse_ast/src/ast_nodes/class_expression.rs @@ -3,7 +3,11 @@ use swc_ecma_ast::ClassExpr; use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_class_expression(&mut self, class_expression: &ClassExpr, node_type: &[u8; 4]) { + pub(crate) fn store_class_expression( + &mut self, + class_expression: &ClassExpr, + node_type: &[u8; 4], + ) { self.store_class_node( node_type, class_expression.ident.as_ref(), diff --git a/rust/parse_ast/src/ast_nodes/conditional_expression.rs b/rust/parse_ast/src/ast_nodes/conditional_expression.rs index 402d14981d2..13333943eae 100644 --- a/rust/parse_ast/src/ast_nodes/conditional_expression.rs +++ b/rust/parse_ast/src/ast_nodes/conditional_expression.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_conditional_expression; impl<'a> AstConverter<'a> { - pub fn store_conditional_expression(&mut self, conditional_expression: &CondExpr) { + pub(crate) fn store_conditional_expression(&mut self, conditional_expression: &CondExpr) { store_conditional_expression!( self, span => conditional_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/continue_statement.rs b/rust/parse_ast/src/ast_nodes/continue_statement.rs index 6614f7910ef..6d652d290e8 100644 --- a/rust/parse_ast/src/ast_nodes/continue_statement.rs +++ b/rust/parse_ast/src/ast_nodes/continue_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_continue_statement; impl<'a> AstConverter<'a> { - pub fn store_continue_statement(&mut self, continue_statement: &ContinueStmt) { + pub(crate) fn store_continue_statement(&mut self, continue_statement: &ContinueStmt) { store_continue_statement!( self, span => continue_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/debugger_statement.rs b/rust/parse_ast/src/ast_nodes/debugger_statement.rs index ac3ea46808f..5b35df24b91 100644 --- a/rust/parse_ast/src/ast_nodes/debugger_statement.rs +++ b/rust/parse_ast/src/ast_nodes/debugger_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_debugger_statement; impl<'a> AstConverter<'a> { - pub fn store_debugger_statement(&mut self, debugger_statement: &DebuggerStmt) { + pub(crate) fn store_debugger_statement(&mut self, debugger_statement: &DebuggerStmt) { store_debugger_statement!(self, span => debugger_statement.span); } } diff --git a/rust/parse_ast/src/ast_nodes/decorator.rs b/rust/parse_ast/src/ast_nodes/decorator.rs index 128fb730d93..61bdc265b4e 100644 --- a/rust/parse_ast/src/ast_nodes/decorator.rs +++ b/rust/parse_ast/src/ast_nodes/decorator.rs @@ -1,9 +1,10 @@ +use swc_ecma_ast::Decorator; + use crate::convert_ast::converter::AstConverter; use crate::store_decorator; -use swc_ecma_ast::Decorator; impl<'a> AstConverter<'a> { - pub fn store_decorator(&mut self, decorator: &Decorator) { + pub(crate) fn store_decorator(&mut self, decorator: &Decorator) { store_decorator!(self, span => decorator.span, expression=>[decorator.expr, convert_expression]); } } diff --git a/rust/parse_ast/src/ast_nodes/directive.rs b/rust/parse_ast/src/ast_nodes/directive.rs index 6290f31519b..ed7bf34d90c 100644 --- a/rust/parse_ast/src/ast_nodes/directive.rs +++ b/rust/parse_ast/src/ast_nodes/directive.rs @@ -5,7 +5,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_directive; impl<'a> AstConverter<'a> { - pub fn store_directive(&mut self, expression_statement: &ExprStmt, directive: &JsWord) { + pub(crate) fn store_directive(&mut self, expression_statement: &ExprStmt, directive: &JsWord) { store_directive!( self, span => expression_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/do_while_statement.rs b/rust/parse_ast/src/ast_nodes/do_while_statement.rs index 01cb24d331f..bf00756ccd3 100644 --- a/rust/parse_ast/src/ast_nodes/do_while_statement.rs +++ b/rust/parse_ast/src/ast_nodes/do_while_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_do_while_statement; impl<'a> AstConverter<'a> { - pub fn store_do_while_statement(&mut self, do_while_statement: &DoWhileStmt) { + pub(crate) fn store_do_while_statement(&mut self, do_while_statement: &DoWhileStmt) { store_do_while_statement!( self, span => do_while_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/empty_statement.rs b/rust/parse_ast/src/ast_nodes/empty_statement.rs index 22d9d147b52..9206fbe3ad5 100644 --- a/rust/parse_ast/src/ast_nodes/empty_statement.rs +++ b/rust/parse_ast/src/ast_nodes/empty_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_empty_statement; impl<'a> AstConverter<'a> { - pub fn store_empty_statement(&mut self, empty_statement: &EmptyStmt) { + pub(crate) fn store_empty_statement(&mut self, empty_statement: &EmptyStmt) { store_empty_statement!(self, span => empty_statement.span); } } diff --git a/rust/parse_ast/src/ast_nodes/export_all_declaration.rs b/rust/parse_ast/src/ast_nodes/export_all_declaration.rs index 57a8c2efe9e..fd0ac5c1c5f 100644 --- a/rust/parse_ast/src/ast_nodes/export_all_declaration.rs +++ b/rust/parse_ast/src/ast_nodes/export_all_declaration.rs @@ -9,7 +9,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_export_all_declaration( + pub(crate) fn store_export_all_declaration( &mut self, span: &Span, source: &Str, @@ -39,7 +39,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, span); } - pub fn convert_export_all(&mut self, export_all: &ExportAll) { + pub(crate) fn convert_export_all(&mut self, export_all: &ExportAll) { self.store_export_all_declaration(&export_all.span, &export_all.src, &export_all.with, None); } } diff --git a/rust/parse_ast/src/ast_nodes/export_default_declaration.rs b/rust/parse_ast/src/ast_nodes/export_default_declaration.rs index d1c557b4f58..c673e2904c6 100644 --- a/rust/parse_ast/src/ast_nodes/export_default_declaration.rs +++ b/rust/parse_ast/src/ast_nodes/export_default_declaration.rs @@ -8,7 +8,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_export_default_declaration( + pub(crate) fn store_export_default_declaration( &mut self, span: &Span, expression: StoredDefaultExportExpression, @@ -42,7 +42,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, span); } - pub fn convert_export_default_declaration( + pub(crate) fn convert_export_default_declaration( &mut self, export_default_declaration: &ExportDefaultDecl, ) { @@ -62,7 +62,7 @@ impl<'a> AstConverter<'a> { ); } - pub fn convert_export_default_expression( + pub(crate) fn convert_export_default_expression( &mut self, export_default_expression: &ExportDefaultExpr, ) { @@ -73,7 +73,7 @@ impl<'a> AstConverter<'a> { } } -pub enum StoredDefaultExportExpression<'a> { +pub(crate) enum StoredDefaultExportExpression<'a> { Expression(&'a Expr), Class(&'a ClassExpr), Function(&'a FnExpr), diff --git a/rust/parse_ast/src/ast_nodes/export_named_declaration.rs b/rust/parse_ast/src/ast_nodes/export_named_declaration.rs index c195afdc097..73cd62eeb6f 100644 --- a/rust/parse_ast/src/ast_nodes/export_named_declaration.rs +++ b/rust/parse_ast/src/ast_nodes/export_named_declaration.rs @@ -9,7 +9,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_export_named_declaration( + pub(crate) fn store_export_named_declaration( &mut self, span: &Span, specifiers: &[ExportSpecifier], diff --git a/rust/parse_ast/src/ast_nodes/export_specifier.rs b/rust/parse_ast/src/ast_nodes/export_specifier.rs index adb8c09cf73..5319746cdeb 100644 --- a/rust/parse_ast/src/ast_nodes/export_specifier.rs +++ b/rust/parse_ast/src/ast_nodes/export_specifier.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_export_specifier; impl<'a> AstConverter<'a> { - pub fn store_export_specifier(&mut self, export_named_specifier: &ExportNamedSpecifier) { + pub(crate) fn store_export_specifier(&mut self, export_named_specifier: &ExportNamedSpecifier) { store_export_specifier!( self, span => &export_named_specifier.span, diff --git a/rust/parse_ast/src/ast_nodes/expression_statement.rs b/rust/parse_ast/src/ast_nodes/expression_statement.rs index 7fed6d83683..b71d13f2aad 100644 --- a/rust/parse_ast/src/ast_nodes/expression_statement.rs +++ b/rust/parse_ast/src/ast_nodes/expression_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_expression_statement; impl<'a> AstConverter<'a> { - pub fn store_expression_statement(&mut self, expression_statement: &ExprStmt) { + pub(crate) fn store_expression_statement(&mut self, expression_statement: &ExprStmt) { store_expression_statement!( self, span => &expression_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/for_in_statement.rs b/rust/parse_ast/src/ast_nodes/for_in_statement.rs index 72bf9e3dda9..8a584c8f49f 100644 --- a/rust/parse_ast/src/ast_nodes/for_in_statement.rs +++ b/rust/parse_ast/src/ast_nodes/for_in_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_for_in_statement; impl<'a> AstConverter<'a> { - pub fn store_for_in_statement(&mut self, for_in_statement: &ForInStmt) { + pub(crate) fn store_for_in_statement(&mut self, for_in_statement: &ForInStmt) { store_for_in_statement!( self, span => &for_in_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/for_of_statement.rs b/rust/parse_ast/src/ast_nodes/for_of_statement.rs index a57e69cfef9..eb27dd7e071 100644 --- a/rust/parse_ast/src/ast_nodes/for_of_statement.rs +++ b/rust/parse_ast/src/ast_nodes/for_of_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::{store_for_of_statement, store_for_of_statement_flags}; impl<'a> AstConverter<'a> { - pub fn store_for_of_statement(&mut self, for_of_statement: &ForOfStmt) { + pub(crate) fn store_for_of_statement(&mut self, for_of_statement: &ForOfStmt) { store_for_of_statement!( self, span => &for_of_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/for_statement.rs b/rust/parse_ast/src/ast_nodes/for_statement.rs index 1d16325e7ba..fd797ee7047 100644 --- a/rust/parse_ast/src/ast_nodes/for_statement.rs +++ b/rust/parse_ast/src/ast_nodes/for_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_for_statement; impl<'a> AstConverter<'a> { - pub fn store_for_statement(&mut self, for_statement: &ForStmt) { + pub(crate) fn store_for_statement(&mut self, for_statement: &ForStmt) { store_for_statement!( self, span => &for_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/identifier.rs b/rust/parse_ast/src/ast_nodes/identifier.rs index a92658dc11c..c42b21574ef 100644 --- a/rust/parse_ast/src/ast_nodes/identifier.rs +++ b/rust/parse_ast/src/ast_nodes/identifier.rs @@ -6,7 +6,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_identifier(&mut self, start: u32, end: u32, name: &str) { + pub(crate) fn store_identifier(&mut self, start: u32, end: u32, name: &str) { let end_position = self.add_type_and_explicit_start(&TYPE_IDENTIFIER, start, IDENTIFIER_RESERVED_BYTES); // name @@ -15,18 +15,18 @@ impl<'a> AstConverter<'a> { self.add_explicit_end(end_position, end); } - pub fn convert_binding_identifier(&mut self, binding_identifier: &BindingIdent) { + pub(crate) fn convert_binding_identifier(&mut self, binding_identifier: &BindingIdent) { self.convert_identifier(&binding_identifier.id); } - pub fn convert_identifier(&mut self, identifier: &Ident) { + pub(crate) fn convert_identifier(&mut self, identifier: &Ident) { self.store_identifier( identifier.span.lo.0 - 1, identifier.span.hi.0 - 1, &identifier.sym, ); } - pub fn convert_identifier_name(&mut self, identifier: &IdentName) { + pub(crate) fn convert_identifier_name(&mut self, identifier: &IdentName) { self.store_identifier( identifier.span.lo.0 - 1, identifier.span.hi.0 - 1, diff --git a/rust/parse_ast/src/ast_nodes/if_statement.rs b/rust/parse_ast/src/ast_nodes/if_statement.rs index e093372cbe9..c8dcb86f319 100644 --- a/rust/parse_ast/src/ast_nodes/if_statement.rs +++ b/rust/parse_ast/src/ast_nodes/if_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_if_statement; impl<'a> AstConverter<'a> { - pub fn store_if_statement(&mut self, if_statement: &IfStmt) { + pub(crate) fn store_if_statement(&mut self, if_statement: &IfStmt) { store_if_statement!( self, span => &if_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/import_attribute.rs b/rust/parse_ast/src/ast_nodes/import_attribute.rs index 829ab14bfe5..fc1842961e7 100644 --- a/rust/parse_ast/src/ast_nodes/import_attribute.rs +++ b/rust/parse_ast/src/ast_nodes/import_attribute.rs @@ -8,7 +8,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_import_attribute(&mut self, key_value_property: &KeyValueProp) { + pub(crate) fn store_import_attribute(&mut self, key_value_property: &KeyValueProp) { // type let end_position = self.add_type_and_start( &TYPE_IMPORT_ATTRIBUTE, @@ -25,7 +25,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, &key_value_property.span()); } - pub fn store_import_attributes( + pub(crate) fn store_import_attributes( &mut self, with: &Option>, reference_position: usize, diff --git a/rust/parse_ast/src/ast_nodes/import_declaration.rs b/rust/parse_ast/src/ast_nodes/import_declaration.rs index af58998c67d..99890338d44 100644 --- a/rust/parse_ast/src/ast_nodes/import_declaration.rs +++ b/rust/parse_ast/src/ast_nodes/import_declaration.rs @@ -7,7 +7,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_import_declaration(&mut self, import_declaration: &ImportDecl) { + pub(crate) fn store_import_declaration(&mut self, import_declaration: &ImportDecl) { let end_position = self.add_type_and_start( &TYPE_IMPORT_DECLARATION, &import_declaration.span, diff --git a/rust/parse_ast/src/ast_nodes/import_default_specifier.rs b/rust/parse_ast/src/ast_nodes/import_default_specifier.rs index 689bd5f4ec2..70c8d5bb7a5 100644 --- a/rust/parse_ast/src/ast_nodes/import_default_specifier.rs +++ b/rust/parse_ast/src/ast_nodes/import_default_specifier.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_import_default_specifier; impl<'a> AstConverter<'a> { - pub fn store_import_default_specifier( + pub(crate) fn store_import_default_specifier( &mut self, import_default_specifier: &ImportDefaultSpecifier, ) { diff --git a/rust/parse_ast/src/ast_nodes/import_expression.rs b/rust/parse_ast/src/ast_nodes/import_expression.rs index 2232b0f5db9..19cff3f0d19 100644 --- a/rust/parse_ast/src/ast_nodes/import_expression.rs +++ b/rust/parse_ast/src/ast_nodes/import_expression.rs @@ -8,7 +8,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_import_expression(&mut self, span: &Span, arguments: &[ExprOrSpread]) { + pub(crate) fn store_import_expression(&mut self, span: &Span, arguments: &[ExprOrSpread]) { let end_position = self.add_type_and_start( &TYPE_IMPORT_EXPRESSION, span, diff --git a/rust/parse_ast/src/ast_nodes/import_namespace_specifier.rs b/rust/parse_ast/src/ast_nodes/import_namespace_specifier.rs index f7ffe93f398..0128b0e5cd5 100644 --- a/rust/parse_ast/src/ast_nodes/import_namespace_specifier.rs +++ b/rust/parse_ast/src/ast_nodes/import_namespace_specifier.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_import_namespace_specifier; impl<'a> AstConverter<'a> { - pub fn store_import_namespace_specifier( + pub(crate) fn store_import_namespace_specifier( &mut self, import_namespace_specifier: &ImportStarAsSpecifier, ) { diff --git a/rust/parse_ast/src/ast_nodes/import_specifier.rs b/rust/parse_ast/src/ast_nodes/import_specifier.rs index 1d02bf0677d..3c162702213 100644 --- a/rust/parse_ast/src/ast_nodes/import_specifier.rs +++ b/rust/parse_ast/src/ast_nodes/import_specifier.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_import_specifier; impl<'a> AstConverter<'a> { - pub fn store_import_specifier(&mut self, import_named_specifier: &ImportNamedSpecifier) { + pub(crate) fn store_import_specifier(&mut self, import_named_specifier: &ImportNamedSpecifier) { store_import_specifier!( self, span => &import_named_specifier.span, diff --git a/rust/parse_ast/src/ast_nodes/jsx_attribute.rs b/rust/parse_ast/src/ast_nodes/jsx_attribute.rs new file mode 100644 index 00000000000..1e36f231e8f --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_attribute.rs @@ -0,0 +1,15 @@ +use swc_ecma_ast::JSXAttr; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_attribute; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_attribute(&mut self, jsx_attribute: &JSXAttr) { + store_jsx_attribute!( + self, + span => jsx_attribute.span, + name => [jsx_attribute.name, convert_jsx_attribute_name], + value => [jsx_attribute.value, convert_jsx_attribute_value] + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_closing_element.rs b/rust/parse_ast/src/ast_nodes/jsx_closing_element.rs new file mode 100644 index 00000000000..c5281e50dcd --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_closing_element.rs @@ -0,0 +1,14 @@ +use swc_ecma_ast::JSXClosingElement; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_closing_element; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_closing_element(&mut self, jsx_closing_element: &JSXClosingElement) { + store_jsx_closing_element!( + self, + span => jsx_closing_element.span, + name => [jsx_closing_element.name, convert_jsx_element_name] + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_closing_fragment.rs b/rust/parse_ast/src/ast_nodes/jsx_closing_fragment.rs new file mode 100644 index 00000000000..650df600f1a --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_closing_fragment.rs @@ -0,0 +1,13 @@ +use swc_ecma_ast::JSXClosingFragment; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_closing_fragment; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_closing_fragment(&mut self, jsx_closing_fragment: &JSXClosingFragment) { + store_jsx_closing_fragment!( + self, + span => jsx_closing_fragment.span + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_element.rs b/rust/parse_ast/src/ast_nodes/jsx_element.rs new file mode 100644 index 00000000000..6a425940f2b --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_element.rs @@ -0,0 +1,16 @@ +use swc_ecma_ast::JSXElement; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_element; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_element(&mut self, jsx_element: &JSXElement) { + store_jsx_element!( + self, + span => jsx_element.span, + openingElement => [jsx_element.opening, store_jsx_opening_element], + children => [jsx_element.children, convert_jsx_element_child], + closingElement => [jsx_element.closing, store_jsx_closing_element] + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_empty_expression.rs b/rust/parse_ast/src/ast_nodes/jsx_empty_expression.rs new file mode 100644 index 00000000000..5b33e034e11 --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_empty_expression.rs @@ -0,0 +1,16 @@ +use crate::convert_ast::converter::ast_constants::{ + JSX_EMPTY_EXPRESSION_RESERVED_BYTES, TYPE_JSX_EMPTY_EXPRESSION, +}; +use crate::convert_ast::converter::AstConverter; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_empty_expression(&mut self, start: u32, end: u32) { + let end_position = self.add_type_and_explicit_start( + &TYPE_JSX_EMPTY_EXPRESSION, + start, + JSX_EMPTY_EXPRESSION_RESERVED_BYTES, + ); + // end + self.add_explicit_end(end_position, end); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_expression_container.rs b/rust/parse_ast/src/ast_nodes/jsx_expression_container.rs new file mode 100644 index 00000000000..d1dc186e0d2 --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_expression_container.rs @@ -0,0 +1,31 @@ +use swc_ecma_ast::{JSXExpr, JSXExprContainer}; + +use crate::convert_ast::converter::ast_constants::{ + JSX_EXPRESSION_CONTAINER_EXPRESSION_OFFSET, JSX_EXPRESSION_CONTAINER_RESERVED_BYTES, + TYPE_JSX_EXPRESSION_CONTAINER, +}; +use crate::convert_ast::converter::AstConverter; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_expression_container(&mut self, jsx_expr_container: &JSXExprContainer) { + let end_position = self.add_type_and_start( + &TYPE_JSX_EXPRESSION_CONTAINER, + &jsx_expr_container.span, + JSX_EXPRESSION_CONTAINER_RESERVED_BYTES, + false, + ); + // expression + self.update_reference_position(end_position + JSX_EXPRESSION_CONTAINER_EXPRESSION_OFFSET); + match &jsx_expr_container.expr { + JSXExpr::Expr(expression) => { + self.convert_expression(expression); + } + JSXExpr::JSXEmptyExpr(jsx_empty_expr) => { + // The span does not consider the size of the container, hence we use the container span + self.store_jsx_empty_expression(jsx_expr_container.span.lo.0, jsx_empty_expr.span.hi.0 - 1); + } + } + // end + self.add_end(end_position, &jsx_expr_container.span); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_fragment.rs b/rust/parse_ast/src/ast_nodes/jsx_fragment.rs new file mode 100644 index 00000000000..0d7a19ded24 --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_fragment.rs @@ -0,0 +1,16 @@ +use swc_ecma_ast::JSXFragment; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_fragment; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_fragment(&mut self, jsx_fragment: &JSXFragment) { + store_jsx_fragment!( + self, + span => jsx_fragment.span, + openingFragment => [jsx_fragment.opening, store_jsx_opening_fragment], + children => [jsx_fragment.children, convert_jsx_element_child], + closingFragment => [jsx_fragment.closing, store_jsx_closing_fragment] + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_identifier.rs b/rust/parse_ast/src/ast_nodes/jsx_identifier.rs new file mode 100644 index 00000000000..0a3ba71c6cc --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_identifier.rs @@ -0,0 +1,14 @@ +use swc_common::Span; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_identifier; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_identifier(&mut self, span: &Span, name: &str) { + store_jsx_identifier!( + self, + span => span, + name => name + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_member_expression.rs b/rust/parse_ast/src/ast_nodes/jsx_member_expression.rs new file mode 100644 index 00000000000..b31460e0b07 --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_member_expression.rs @@ -0,0 +1,29 @@ +use swc_ecma_ast::JSXMemberExpr; + +use crate::convert_ast::converter::ast_constants::{ + JSX_MEMBER_EXPRESSION_OBJECT_OFFSET, JSX_MEMBER_EXPRESSION_PROPERTY_OFFSET, + JSX_MEMBER_EXPRESSION_RESERVED_BYTES, TYPE_JSX_MEMBER_EXPRESSION, +}; +use crate::convert_ast::converter::AstConverter; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_member_expression(&mut self, jsx_member_expression: &JSXMemberExpr) { + let end_position = self.add_type_and_start( + &TYPE_JSX_MEMBER_EXPRESSION, + &jsx_member_expression.span, + JSX_MEMBER_EXPRESSION_RESERVED_BYTES, + false, + ); + // object + self.update_reference_position(end_position + JSX_MEMBER_EXPRESSION_OBJECT_OFFSET); + self.convert_jsx_object(&jsx_member_expression.obj); + // property + self.update_reference_position(end_position + JSX_MEMBER_EXPRESSION_PROPERTY_OFFSET); + self.store_jsx_identifier( + &jsx_member_expression.prop.span, + &jsx_member_expression.prop.sym, + ); + // end + self.add_end(end_position, &jsx_member_expression.span); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_namespaced_name.rs b/rust/parse_ast/src/ast_nodes/jsx_namespaced_name.rs new file mode 100644 index 00000000000..ed61746af50 --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_namespaced_name.rs @@ -0,0 +1,29 @@ +use swc_ecma_ast::JSXNamespacedName; + +use crate::convert_ast::converter::ast_constants::{ + JSX_NAMESPACED_NAME_NAMESPACE_OFFSET, JSX_NAMESPACED_NAME_NAME_OFFSET, + JSX_NAMESPACED_NAME_RESERVED_BYTES, TYPE_JSX_NAMESPACED_NAME, +}; +use crate::convert_ast::converter::AstConverter; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_namespaced_name(&mut self, jsx_namespaced_name: &JSXNamespacedName) { + let end_position = self.add_type_and_start( + &TYPE_JSX_NAMESPACED_NAME, + &jsx_namespaced_name.ns.span, + JSX_NAMESPACED_NAME_RESERVED_BYTES, + false, + ); + // namespace + self.update_reference_position(end_position + JSX_NAMESPACED_NAME_NAMESPACE_OFFSET); + self.store_jsx_identifier(&jsx_namespaced_name.ns.span, &jsx_namespaced_name.ns.sym); + // name + self.update_reference_position(end_position + JSX_NAMESPACED_NAME_NAME_OFFSET); + self.store_jsx_identifier( + &jsx_namespaced_name.name.span, + &jsx_namespaced_name.name.sym, + ); + // end + self.add_end(end_position, &jsx_namespaced_name.name.span); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_opening_element.rs b/rust/parse_ast/src/ast_nodes/jsx_opening_element.rs new file mode 100644 index 00000000000..1bd4b1025b1 --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_opening_element.rs @@ -0,0 +1,43 @@ +use swc_common::Spanned; +use swc_ecma_ast::JSXOpeningElement; + +use crate::convert_ast::converter::ast_constants::{ + JSX_OPENING_ELEMENT_ATTRIBUTES_OFFSET, JSX_OPENING_ELEMENT_NAME_OFFSET, + JSX_OPENING_ELEMENT_RESERVED_BYTES, TYPE_JSX_OPENING_ELEMENT, +}; +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_opening_element_flags; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_opening_element(&mut self, jsx_opening_element: &JSXOpeningElement) { + let end_position = self.add_type_and_start( + &TYPE_JSX_OPENING_ELEMENT, + &jsx_opening_element.span, + JSX_OPENING_ELEMENT_RESERVED_BYTES, + false, + ); + // flags + store_jsx_opening_element_flags!( + self, + end_position, + selfClosing => jsx_opening_element.self_closing + ); + // name + self.update_reference_position(end_position + JSX_OPENING_ELEMENT_NAME_OFFSET); + self.convert_jsx_element_name(&jsx_opening_element.name); + // attributes + let mut previous_element_end = jsx_opening_element.name.span().hi.0; + self.convert_item_list_with_state( + &jsx_opening_element.attrs, + &mut previous_element_end, + end_position + JSX_OPENING_ELEMENT_ATTRIBUTES_OFFSET, + |ast_converter, jsx_attribute, previous_end| { + ast_converter.convert_jsx_attribute_or_spread(jsx_attribute, *previous_end); + *previous_end = jsx_attribute.span().hi.0; + true + }, + ); + // end + self.add_end(end_position, &jsx_opening_element.span); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_opening_fragment.rs b/rust/parse_ast/src/ast_nodes/jsx_opening_fragment.rs new file mode 100644 index 00000000000..acb52da45ee --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_opening_fragment.rs @@ -0,0 +1,13 @@ +use swc_ecma_ast::JSXOpeningFragment; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_opening_fragment; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_opening_fragment(&mut self, jsx_opening_fragment: &JSXOpeningFragment) { + store_jsx_opening_fragment!( + self, + span => jsx_opening_fragment.span + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_spread_attribute.rs b/rust/parse_ast/src/ast_nodes/jsx_spread_attribute.rs new file mode 100644 index 00000000000..86eb4dfcd96 --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_spread_attribute.rs @@ -0,0 +1,32 @@ +use swc_common::Spanned; +use swc_ecma_ast::SpreadElement; + +use crate::convert_ast::converter::analyze_code::find_first_occurrence_outside_comment; +use crate::convert_ast::converter::ast_constants::{ + JSX_SPREAD_ATTRIBUTE_ARGUMENT_OFFSET, JSX_SPREAD_ATTRIBUTE_RESERVED_BYTES, + TYPE_JSX_SPREAD_ATTRIBUTE, +}; +use crate::convert_ast::converter::AstConverter; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_spread_attribute( + &mut self, + spread_element: &SpreadElement, + previous_element_end: u32, + ) { + let end_position = self.add_type_and_explicit_start( + &TYPE_JSX_SPREAD_ATTRIBUTE, + find_first_occurrence_outside_comment(self.code, b'{', previous_element_end), + JSX_SPREAD_ATTRIBUTE_RESERVED_BYTES, + ); + // argument + self.update_reference_position(end_position + JSX_SPREAD_ATTRIBUTE_ARGUMENT_OFFSET); + self.convert_expression(&spread_element.expr); + // end + self.add_explicit_end( + end_position, + find_first_occurrence_outside_comment(self.code, b'}', spread_element.expr.span().hi.0 - 1) + + 1, + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_spread_child.rs b/rust/parse_ast/src/ast_nodes/jsx_spread_child.rs new file mode 100644 index 00000000000..b65bc4961de --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_spread_child.rs @@ -0,0 +1,14 @@ +use swc_ecma_ast::JSXSpreadChild; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_spread_child; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_spread_child(&mut self, jsx_spread_child: &JSXSpreadChild) { + store_jsx_spread_child!( + self, + span => jsx_spread_child.span, + expression => [jsx_spread_child.expr, convert_expression] + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/jsx_text.rs b/rust/parse_ast/src/ast_nodes/jsx_text.rs new file mode 100644 index 00000000000..6bf72a1e34e --- /dev/null +++ b/rust/parse_ast/src/ast_nodes/jsx_text.rs @@ -0,0 +1,15 @@ +use swc_ecma_ast::JSXText; + +use crate::convert_ast::converter::AstConverter; +use crate::store_jsx_text; + +impl<'a> AstConverter<'a> { + pub(crate) fn store_jsx_text(&mut self, jsx_text: &JSXText) { + store_jsx_text!( + self, + span => jsx_text.span, + value => &jsx_text.value, + raw => &jsx_text.raw + ); + } +} diff --git a/rust/parse_ast/src/ast_nodes/labeled_statement.rs b/rust/parse_ast/src/ast_nodes/labeled_statement.rs index 0f2bd3cd07e..f2a8dc44ba3 100644 --- a/rust/parse_ast/src/ast_nodes/labeled_statement.rs +++ b/rust/parse_ast/src/ast_nodes/labeled_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_labeled_statement; impl<'a> AstConverter<'a> { - pub fn store_labeled_statement(&mut self, labeled_statement: &LabeledStmt) { + pub(crate) fn store_labeled_statement(&mut self, labeled_statement: &LabeledStmt) { store_labeled_statement!( self, span => &labeled_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/literal_big_int.rs b/rust/parse_ast/src/ast_nodes/literal_big_int.rs index 5a98704fd3e..2ca27c3aa26 100644 --- a/rust/parse_ast/src/ast_nodes/literal_big_int.rs +++ b/rust/parse_ast/src/ast_nodes/literal_big_int.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_literal_big_int; impl<'a> AstConverter<'a> { - pub fn store_literal_bigint(&mut self, bigint: &BigInt) { + pub(crate) fn store_literal_bigint(&mut self, bigint: &BigInt) { store_literal_big_int!( self, span => &bigint.span, diff --git a/rust/parse_ast/src/ast_nodes/literal_boolean.rs b/rust/parse_ast/src/ast_nodes/literal_boolean.rs index 9c84f9dfb5a..41e22f75c31 100644 --- a/rust/parse_ast/src/ast_nodes/literal_boolean.rs +++ b/rust/parse_ast/src/ast_nodes/literal_boolean.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::{store_literal_boolean, store_literal_boolean_flags}; impl<'a> AstConverter<'a> { - pub fn store_literal_boolean(&mut self, literal: &Bool) { + pub(crate) fn store_literal_boolean(&mut self, literal: &Bool) { store_literal_boolean!( self, span => &literal.span, diff --git a/rust/parse_ast/src/ast_nodes/literal_null.rs b/rust/parse_ast/src/ast_nodes/literal_null.rs index 9c46f838c71..541007ff3a5 100644 --- a/rust/parse_ast/src/ast_nodes/literal_null.rs +++ b/rust/parse_ast/src/ast_nodes/literal_null.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_literal_null; impl<'a> AstConverter<'a> { - pub fn store_literal_null(&mut self, literal: &Null) { + pub(crate) fn store_literal_null(&mut self, literal: &Null) { store_literal_null!( self, span => &literal.span diff --git a/rust/parse_ast/src/ast_nodes/literal_number.rs b/rust/parse_ast/src/ast_nodes/literal_number.rs index 64cc5fa32ec..6c8d9aa3d37 100644 --- a/rust/parse_ast/src/ast_nodes/literal_number.rs +++ b/rust/parse_ast/src/ast_nodes/literal_number.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_literal_number; impl<'a> AstConverter<'a> { - pub fn store_literal_number(&mut self, literal: &Number) { + pub(crate) fn store_literal_number(&mut self, literal: &Number) { store_literal_number!( self, span => &literal.span, diff --git a/rust/parse_ast/src/ast_nodes/literal_reg_exp.rs b/rust/parse_ast/src/ast_nodes/literal_reg_exp.rs index 3e730e4f4d5..f731d3efb20 100644 --- a/rust/parse_ast/src/ast_nodes/literal_reg_exp.rs +++ b/rust/parse_ast/src/ast_nodes/literal_reg_exp.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_literal_reg_exp; impl<'a> AstConverter<'a> { - pub fn store_literal_regex(&mut self, regex: &Regex) { + pub(crate) fn store_literal_regex(&mut self, regex: &Regex) { store_literal_reg_exp!( self, span => ®ex.span, diff --git a/rust/parse_ast/src/ast_nodes/literal_string.rs b/rust/parse_ast/src/ast_nodes/literal_string.rs index 0b475512e23..373db0b5a26 100644 --- a/rust/parse_ast/src/ast_nodes/literal_string.rs +++ b/rust/parse_ast/src/ast_nodes/literal_string.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_literal_string; impl<'a> AstConverter<'a> { - pub fn store_literal_string(&mut self, literal: &Str) { + pub(crate) fn store_literal_string(&mut self, literal: &Str) { store_literal_string!( self, span => &literal.span, diff --git a/rust/parse_ast/src/ast_nodes/member_expression.rs b/rust/parse_ast/src/ast_nodes/member_expression.rs index 8f9ed91244c..6b0b23db2e7 100644 --- a/rust/parse_ast/src/ast_nodes/member_expression.rs +++ b/rust/parse_ast/src/ast_nodes/member_expression.rs @@ -12,7 +12,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_member_expression_flags; impl<'a> AstConverter<'a> { - pub fn store_member_expression( + pub(crate) fn store_member_expression( &mut self, span: &Span, is_optional: bool, @@ -63,7 +63,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, span); } - pub fn convert_member_expression( + pub(crate) fn convert_member_expression( &mut self, member_expression: &MemberExpr, is_optional: bool, @@ -82,7 +82,7 @@ impl<'a> AstConverter<'a> { ); } - pub fn convert_super_property(&mut self, super_property: &SuperPropExpr) { + pub(crate) fn convert_super_property(&mut self, super_property: &SuperPropExpr) { self.store_member_expression( &super_property.span, false, @@ -98,13 +98,13 @@ impl<'a> AstConverter<'a> { } } -pub enum MemberOrSuperProp<'a> { +pub(crate) enum MemberOrSuperProp<'a> { Identifier(&'a IdentName), PrivateName(&'a PrivateName), Computed(&'a ComputedPropName), } -pub enum ExpressionOrSuper<'a> { +pub(crate) enum ExpressionOrSuper<'a> { Expression(&'a Expr), Super(&'a Super), } diff --git a/rust/parse_ast/src/ast_nodes/meta_property.rs b/rust/parse_ast/src/ast_nodes/meta_property.rs index 209886668bd..35d4d826085 100644 --- a/rust/parse_ast/src/ast_nodes/meta_property.rs +++ b/rust/parse_ast/src/ast_nodes/meta_property.rs @@ -7,7 +7,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_meta_property(&mut self, meta_property_expression: &MetaPropExpr) { + pub(crate) fn store_meta_property(&mut self, meta_property_expression: &MetaPropExpr) { let end_position = self.add_type_and_start( &TYPE_META_PROPERTY, &meta_property_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/method_definition.rs b/rust/parse_ast/src/ast_nodes/method_definition.rs index e7fd91e2ff8..fd5b8d02102 100644 --- a/rust/parse_ast/src/ast_nodes/method_definition.rs +++ b/rust/parse_ast/src/ast_nodes/method_definition.rs @@ -17,7 +17,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_method_definition_flags; impl<'a> AstConverter<'a> { - pub fn store_method_definition( + pub(crate) fn store_method_definition( &mut self, span: &Span, kind: &MethodKind, @@ -81,7 +81,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, span); } - pub fn convert_constructor(&mut self, constructor: &Constructor) { + pub(crate) fn convert_constructor(&mut self, constructor: &Constructor) { let end_position = self.add_type_and_start( &TYPE_METHOD_DEFINITION, &constructor.span, @@ -130,7 +130,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, &constructor.span); } - pub fn convert_method(&mut self, method: &ClassMethod) { + pub(crate) fn convert_method(&mut self, method: &ClassMethod) { self.store_method_definition( &method.span, &method.kind, @@ -141,7 +141,7 @@ impl<'a> AstConverter<'a> { ); } - pub fn convert_private_method(&mut self, private_method: &PrivateMethod) { + pub(crate) fn convert_private_method(&mut self, private_method: &PrivateMethod) { self.store_method_definition( &private_method.span, &private_method.kind, @@ -153,7 +153,7 @@ impl<'a> AstConverter<'a> { } } -pub enum PropOrPrivateName<'a> { +pub(crate) enum PropOrPrivateName<'a> { PropName(&'a PropName), PrivateName(&'a PrivateName), } diff --git a/rust/parse_ast/src/ast_nodes/mod.rs b/rust/parse_ast/src/ast_nodes/mod.rs index 2284cc3e01b..ab7560659a2 100644 --- a/rust/parse_ast/src/ast_nodes/mod.rs +++ b/rust/parse_ast/src/ast_nodes/mod.rs @@ -1,81 +1,96 @@ -pub mod array_expression; -pub mod array_pattern; -pub mod arrow_function_expression; -pub mod assignment_expression; -pub mod assignment_pattern; -pub mod await_expression; -pub mod binary_expression; -pub mod block_statement; -pub mod break_statement; -pub mod call_expression; -pub mod catch_clause; -pub mod chain_expression; -pub mod class_body; -pub mod class_declaration; -pub mod class_expression; -pub mod conditional_expression; -pub mod continue_statement; -pub mod debugger_statement; -pub mod decorator; -pub mod directive; -pub mod do_while_statement; -pub mod empty_statement; -pub mod export_all_declaration; -pub mod export_default_declaration; -pub mod export_named_declaration; -pub mod export_specifier; -pub mod expression_statement; -pub mod for_in_statement; -pub mod for_of_statement; -pub mod for_statement; -pub mod function_declaration; -pub mod function_expression; -pub mod identifier; -pub mod if_statement; -pub mod import_attribute; -pub mod import_declaration; -pub mod import_default_specifier; -pub mod import_expression; -pub mod import_namespace_specifier; -pub mod import_specifier; -pub mod labeled_statement; -pub mod literal_big_int; -pub mod literal_boolean; -pub mod literal_null; -pub mod literal_number; -pub mod literal_reg_exp; -pub mod literal_string; -pub mod logical_expression; -pub mod member_expression; -pub mod meta_property; -pub mod method_definition; -pub mod new_expression; -pub mod object_expression; -pub mod object_pattern; -pub mod panic_error; -pub mod parse_error; -pub mod private_identifier; -pub mod program; -pub mod property; -pub mod property_definition; -pub mod rest_element; -pub mod return_statement; -pub mod sequence_expression; -pub mod shared; -pub mod spread_element; -pub mod static_block; -pub mod super_element; -pub mod switch_case; -pub mod switch_statement; -pub mod tagged_template_expression; -pub mod template_element; -pub mod template_literal; -pub mod this_expression; -pub mod throw_statement; -pub mod try_statement; -pub mod unary_expression; -pub mod update_expression; -pub mod variable_declaration; -pub mod variable_declarator; -pub mod while_statement; -pub mod yield_expression; +pub(crate) mod array_expression; +pub(crate) mod array_pattern; +pub(crate) mod arrow_function_expression; +pub(crate) mod assignment_expression; +pub(crate) mod assignment_pattern; +pub(crate) mod await_expression; +pub(crate) mod binary_expression; +pub(crate) mod block_statement; +pub(crate) mod break_statement; +pub(crate) mod call_expression; +pub(crate) mod catch_clause; +pub(crate) mod chain_expression; +pub(crate) mod class_body; +pub(crate) mod class_declaration; +pub(crate) mod class_expression; +pub(crate) mod conditional_expression; +pub(crate) mod continue_statement; +pub(crate) mod debugger_statement; +pub(crate) mod decorator; +pub(crate) mod directive; +pub(crate) mod do_while_statement; +pub(crate) mod empty_statement; +pub(crate) mod export_all_declaration; +pub(crate) mod export_default_declaration; +pub(crate) mod export_named_declaration; +pub(crate) mod export_specifier; +pub(crate) mod expression_statement; +pub(crate) mod for_in_statement; +pub(crate) mod for_of_statement; +pub(crate) mod for_statement; +pub(crate) mod function_declaration; +pub(crate) mod function_expression; +pub(crate) mod identifier; +pub(crate) mod if_statement; +pub(crate) mod import_attribute; +pub(crate) mod import_declaration; +pub(crate) mod import_default_specifier; +pub(crate) mod import_expression; +pub(crate) mod import_namespace_specifier; +pub(crate) mod import_specifier; +pub(crate) mod jsx_attribute; +pub(crate) mod jsx_closing_element; +pub(crate) mod jsx_closing_fragment; +pub(crate) mod jsx_element; +pub(crate) mod jsx_empty_expression; +pub(crate) mod jsx_expression_container; +pub(crate) mod jsx_fragment; +pub(crate) mod jsx_identifier; +pub(crate) mod jsx_member_expression; +pub(crate) mod jsx_namespaced_name; +pub(crate) mod jsx_opening_element; +pub(crate) mod jsx_opening_fragment; +pub(crate) mod jsx_spread_attribute; +pub(crate) mod jsx_spread_child; +pub(crate) mod jsx_text; +pub(crate) mod labeled_statement; +pub(crate) mod literal_big_int; +pub(crate) mod literal_boolean; +pub(crate) mod literal_null; +pub(crate) mod literal_number; +pub(crate) mod literal_reg_exp; +pub(crate) mod literal_string; +pub(crate) mod logical_expression; +pub(crate) mod member_expression; +pub(crate) mod meta_property; +pub(crate) mod method_definition; +pub(crate) mod new_expression; +pub(crate) mod object_expression; +pub(crate) mod object_pattern; +pub(crate) mod panic_error; +pub(crate) mod parse_error; +pub(crate) mod private_identifier; +pub(crate) mod program; +pub(crate) mod property; +pub(crate) mod property_definition; +pub(crate) mod rest_element; +pub(crate) mod return_statement; +pub(crate) mod sequence_expression; +pub(crate) mod shared; +pub(crate) mod spread_element; +pub(crate) mod static_block; +pub(crate) mod super_element; +pub(crate) mod switch_case; +pub(crate) mod switch_statement; +pub(crate) mod tagged_template_expression; +pub(crate) mod template_element; +pub(crate) mod template_literal; +pub(crate) mod this_expression; +pub(crate) mod throw_statement; +pub(crate) mod try_statement; +pub(crate) mod unary_expression; +pub(crate) mod update_expression; +pub(crate) mod variable_declaration; +pub(crate) mod variable_declarator; +pub(crate) mod while_statement; +pub(crate) mod yield_expression; diff --git a/rust/parse_ast/src/ast_nodes/new_expression.rs b/rust/parse_ast/src/ast_nodes/new_expression.rs index 9b16a35bfe1..20a01e21e1f 100644 --- a/rust/parse_ast/src/ast_nodes/new_expression.rs +++ b/rust/parse_ast/src/ast_nodes/new_expression.rs @@ -8,7 +8,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::{convert_annotation, AstConverter}; impl<'a> AstConverter<'a> { - pub fn store_new_expression(&mut self, new_expression: &NewExpr) { + pub(crate) fn store_new_expression(&mut self, new_expression: &NewExpr) { let end_position = self.add_type_and_start( &TYPE_NEW_EXPRESSION, &new_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/object_expression.rs b/rust/parse_ast/src/ast_nodes/object_expression.rs index 72e07f702fc..647ac19be59 100644 --- a/rust/parse_ast/src/ast_nodes/object_expression.rs +++ b/rust/parse_ast/src/ast_nodes/object_expression.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_object_expression; impl<'a> AstConverter<'a> { - pub fn store_object_expression(&mut self, object_literal: &ObjectLit) { + pub(crate) fn store_object_expression(&mut self, object_literal: &ObjectLit) { store_object_expression!( self, span => &object_literal.span, diff --git a/rust/parse_ast/src/ast_nodes/object_pattern.rs b/rust/parse_ast/src/ast_nodes/object_pattern.rs index 22a958bd90d..16b3dc1ff7f 100644 --- a/rust/parse_ast/src/ast_nodes/object_pattern.rs +++ b/rust/parse_ast/src/ast_nodes/object_pattern.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_object_pattern; impl<'a> AstConverter<'a> { - pub fn store_object_pattern(&mut self, object_pattern: &ObjectPat) { + pub(crate) fn store_object_pattern(&mut self, object_pattern: &ObjectPat) { store_object_pattern!( self, span => &object_pattern.span, diff --git a/rust/parse_ast/src/ast_nodes/panic_error.rs b/rust/parse_ast/src/ast_nodes/panic_error.rs index d53ae8bf563..bf290bcb4d7 100644 --- a/rust/parse_ast/src/ast_nodes/panic_error.rs +++ b/rust/parse_ast/src/ast_nodes/panic_error.rs @@ -3,7 +3,7 @@ use crate::convert_ast::converter::ast_constants::{ }; use crate::convert_ast::converter::{convert_string, update_reference_position}; -pub fn get_panic_error_buffer(message: &str) -> Vec { +pub(crate) fn get_panic_error_buffer(message: &str) -> Vec { // type let mut buffer = TYPE_PANIC_ERROR.to_vec(); // reserve for start and end even though they are unused diff --git a/rust/parse_ast/src/ast_nodes/parse_error.rs b/rust/parse_ast/src/ast_nodes/parse_error.rs index 440624a7707..22026546025 100644 --- a/rust/parse_ast/src/ast_nodes/parse_error.rs +++ b/rust/parse_ast/src/ast_nodes/parse_error.rs @@ -3,7 +3,7 @@ use crate::convert_ast::converter::ast_constants::{ }; use crate::convert_ast::converter::update_reference_position; -pub fn get_parse_error_buffer(error_buffer: &[u8], utf_16_pos: &u32) -> Vec { +pub(crate) fn get_parse_error_buffer(error_buffer: &[u8], utf_16_pos: &u32) -> Vec { // type let mut buffer = TYPE_PARSE_ERROR.to_vec(); // start diff --git a/rust/parse_ast/src/ast_nodes/private_identifier.rs b/rust/parse_ast/src/ast_nodes/private_identifier.rs index d1797c9a7c1..2ae38cd4d87 100644 --- a/rust/parse_ast/src/ast_nodes/private_identifier.rs +++ b/rust/parse_ast/src/ast_nodes/private_identifier.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_private_identifier; impl<'a> AstConverter<'a> { - pub fn store_private_identifier(&mut self, private_name: &PrivateName) { + pub(crate) fn store_private_identifier(&mut self, private_name: &PrivateName) { store_private_identifier!( self, span => &private_name.span, diff --git a/rust/parse_ast/src/ast_nodes/program.rs b/rust/parse_ast/src/ast_nodes/program.rs index 60c34485cba..f83106328a9 100644 --- a/rust/parse_ast/src/ast_nodes/program.rs +++ b/rust/parse_ast/src/ast_nodes/program.rs @@ -6,7 +6,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::{convert_annotation, AstConverter}; impl<'a> AstConverter<'a> { - pub fn store_program(&mut self, body: ModuleItemsOrStatements) { + pub(crate) fn store_program(&mut self, body: ModuleItemsOrStatements) { let end_position = self.add_type_and_explicit_start(&TYPE_PROGRAM, 0u32, PROGRAM_RESERVED_BYTES); // body @@ -71,7 +71,7 @@ impl<'a> AstConverter<'a> { } } - pub fn convert_program(&mut self, node: &Program) { + pub(crate) fn convert_program(&mut self, node: &Program) { match node { Program::Module(module) => { self.store_program(ModuleItemsOrStatements::ModuleItems(&module.body)); @@ -83,7 +83,7 @@ impl<'a> AstConverter<'a> { } } -pub enum ModuleItemsOrStatements<'a> { +pub(crate) enum ModuleItemsOrStatements<'a> { ModuleItems(&'a Vec), Statements(&'a Vec), } diff --git a/rust/parse_ast/src/ast_nodes/property.rs b/rust/parse_ast/src/ast_nodes/property.rs index f2c9b866d75..fda0288293b 100644 --- a/rust/parse_ast/src/ast_nodes/property.rs +++ b/rust/parse_ast/src/ast_nodes/property.rs @@ -15,7 +15,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_property_flags; impl<'a> AstConverter<'a> { - pub fn convert_property(&mut self, property: &Prop) { + pub(crate) fn convert_property(&mut self, property: &Prop) { match property { Prop::Getter(getter_property) => self.convert_getter_property(getter_property), Prop::KeyValue(key_value_property) => self.convert_key_value_property(key_value_property), @@ -26,7 +26,7 @@ impl<'a> AstConverter<'a> { } } - pub fn convert_assignment_pattern_property( + pub(crate) fn convert_assignment_pattern_property( &mut self, assignment_pattern_property: &AssignPatProp, ) { @@ -37,7 +37,7 @@ impl<'a> AstConverter<'a> { ); } - pub fn convert_key_value_pattern_property( + pub(crate) fn convert_key_value_pattern_property( &mut self, key_value_pattern_property: &KeyValuePatProp, ) { @@ -242,7 +242,7 @@ impl<'a> AstConverter<'a> { } #[derive(Spanned)] -pub enum PatternOrExpression<'a> { +pub(crate) enum PatternOrExpression<'a> { Pattern(&'a Pat), Expression(&'a Expr), } diff --git a/rust/parse_ast/src/ast_nodes/property_definition.rs b/rust/parse_ast/src/ast_nodes/property_definition.rs index 24d8c89d397..4ef508d7270 100644 --- a/rust/parse_ast/src/ast_nodes/property_definition.rs +++ b/rust/parse_ast/src/ast_nodes/property_definition.rs @@ -10,7 +10,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_property_definition_flags; impl<'a> AstConverter<'a> { - pub fn store_property_definition( + pub(crate) fn store_property_definition( &mut self, span: &Span, is_computed: bool, @@ -53,7 +53,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, span); } - pub fn convert_class_property(&mut self, class_property: &ClassProp) { + pub(crate) fn convert_class_property(&mut self, class_property: &ClassProp) { self.store_property_definition( &class_property.span, matches!(&class_property.key, PropName::Computed(_)), @@ -64,7 +64,7 @@ impl<'a> AstConverter<'a> { ); } - pub fn convert_private_property(&mut self, private_property: &PrivateProp) { + pub(crate) fn convert_private_property(&mut self, private_property: &PrivateProp) { self.store_property_definition( &private_property.span, false, diff --git a/rust/parse_ast/src/ast_nodes/rest_element.rs b/rust/parse_ast/src/ast_nodes/rest_element.rs index 2bf6d1fe6be..9176410c24a 100644 --- a/rust/parse_ast/src/ast_nodes/rest_element.rs +++ b/rust/parse_ast/src/ast_nodes/rest_element.rs @@ -6,7 +6,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_rest_element(&mut self, rest_pattern: &RestPat) { + pub(crate) fn store_rest_element(&mut self, rest_pattern: &RestPat) { let end_position = self.add_type_and_start( &TYPE_REST_ELEMENT, &rest_pattern.dot3_token, diff --git a/rust/parse_ast/src/ast_nodes/return_statement.rs b/rust/parse_ast/src/ast_nodes/return_statement.rs index 5a0870bd564..0e41a910b77 100644 --- a/rust/parse_ast/src/ast_nodes/return_statement.rs +++ b/rust/parse_ast/src/ast_nodes/return_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_return_statement; impl<'a> AstConverter<'a> { - pub fn store_return_statement(&mut self, return_statement: &ReturnStmt) { + pub(crate) fn store_return_statement(&mut self, return_statement: &ReturnStmt) { store_return_statement!( self, span => return_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/sequence_expression.rs b/rust/parse_ast/src/ast_nodes/sequence_expression.rs index fa69c151e4d..f179cdbf36e 100644 --- a/rust/parse_ast/src/ast_nodes/sequence_expression.rs +++ b/rust/parse_ast/src/ast_nodes/sequence_expression.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_sequence_expression; impl<'a> AstConverter<'a> { - pub fn store_sequence_expression(&mut self, sequence_expression: &SeqExpr) { + pub(crate) fn store_sequence_expression(&mut self, sequence_expression: &SeqExpr) { store_sequence_expression!( self, span => &sequence_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/shared/class_node.rs b/rust/parse_ast/src/ast_nodes/shared/class_node.rs index 67d6fa32bd3..068b7aaba5b 100644 --- a/rust/parse_ast/src/ast_nodes/shared/class_node.rs +++ b/rust/parse_ast/src/ast_nodes/shared/class_node.rs @@ -9,7 +9,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_class_node( + pub(crate) fn store_class_node( &mut self, node_type: &[u8; 4], identifier: Option<&Ident>, diff --git a/rust/parse_ast/src/ast_nodes/shared/function_node.rs b/rust/parse_ast/src/ast_nodes/shared/function_node.rs index 34d868e5f0e..665411e7203 100644 --- a/rust/parse_ast/src/ast_nodes/shared/function_node.rs +++ b/rust/parse_ast/src/ast_nodes/shared/function_node.rs @@ -31,7 +31,7 @@ impl<'a> AstConverter<'a> { } #[allow(clippy::too_many_arguments)] - pub fn store_function_node( + pub(crate) fn store_function_node( &mut self, node_type: &[u8; 4], start: u32, diff --git a/rust/parse_ast/src/ast_nodes/spread_element.rs b/rust/parse_ast/src/ast_nodes/spread_element.rs index 8bd63a7bbc3..0a6b9dfbd96 100644 --- a/rust/parse_ast/src/ast_nodes/spread_element.rs +++ b/rust/parse_ast/src/ast_nodes/spread_element.rs @@ -7,7 +7,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_spread_element(&mut self, dot_span: &Span, argument: &Expr) { + pub(crate) fn store_spread_element(&mut self, dot_span: &Span, argument: &Expr) { let end_position = self.add_type_and_start( &TYPE_SPREAD_ELEMENT, dot_span, @@ -21,7 +21,7 @@ impl<'a> AstConverter<'a> { self.add_end(end_position, &argument.span()); } - pub fn convert_spread_element(&mut self, spread_element: &SpreadElement) { + pub(crate) fn convert_spread_element(&mut self, spread_element: &SpreadElement) { self.store_spread_element(&spread_element.dot3_token, &spread_element.expr); } } diff --git a/rust/parse_ast/src/ast_nodes/static_block.rs b/rust/parse_ast/src/ast_nodes/static_block.rs index 43d858cc3b7..4b0e7d46105 100644 --- a/rust/parse_ast/src/ast_nodes/static_block.rs +++ b/rust/parse_ast/src/ast_nodes/static_block.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_static_block; impl<'a> AstConverter<'a> { - pub fn store_static_block(&mut self, static_block: &StaticBlock) { + pub(crate) fn store_static_block(&mut self, static_block: &StaticBlock) { store_static_block!( self, span => &static_block.span, diff --git a/rust/parse_ast/src/ast_nodes/super_element.rs b/rust/parse_ast/src/ast_nodes/super_element.rs index cb5df082095..1ca9c66767e 100644 --- a/rust/parse_ast/src/ast_nodes/super_element.rs +++ b/rust/parse_ast/src/ast_nodes/super_element.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_super_element; impl<'a> AstConverter<'a> { - pub fn store_super_element(&mut self, super_token: &Super) { + pub(crate) fn store_super_element(&mut self, super_token: &Super) { store_super_element!( self, span => &super_token.span diff --git a/rust/parse_ast/src/ast_nodes/switch_case.rs b/rust/parse_ast/src/ast_nodes/switch_case.rs index e1a27867466..3cd92797cab 100644 --- a/rust/parse_ast/src/ast_nodes/switch_case.rs +++ b/rust/parse_ast/src/ast_nodes/switch_case.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_switch_case; impl<'a> AstConverter<'a> { - pub fn store_switch_case(&mut self, switch_case: &SwitchCase) { + pub(crate) fn store_switch_case(&mut self, switch_case: &SwitchCase) { store_switch_case!( self, span => &switch_case.span, diff --git a/rust/parse_ast/src/ast_nodes/switch_statement.rs b/rust/parse_ast/src/ast_nodes/switch_statement.rs index 2c6ba5684a4..3dd18303d15 100644 --- a/rust/parse_ast/src/ast_nodes/switch_statement.rs +++ b/rust/parse_ast/src/ast_nodes/switch_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_switch_statement; impl<'a> AstConverter<'a> { - pub fn store_switch_statement(&mut self, switch_statement: &SwitchStmt) { + pub(crate) fn store_switch_statement(&mut self, switch_statement: &SwitchStmt) { store_switch_statement!( self, span => &switch_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/tagged_template_expression.rs b/rust/parse_ast/src/ast_nodes/tagged_template_expression.rs index c7cdcacfae1..ac7fdbc01ef 100644 --- a/rust/parse_ast/src/ast_nodes/tagged_template_expression.rs +++ b/rust/parse_ast/src/ast_nodes/tagged_template_expression.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_tagged_template_expression; impl<'a> AstConverter<'a> { - pub fn store_tagged_template_expression(&mut self, tagged_template: &TaggedTpl) { + pub(crate) fn store_tagged_template_expression(&mut self, tagged_template: &TaggedTpl) { store_tagged_template_expression!( self, span => &tagged_template.span, diff --git a/rust/parse_ast/src/ast_nodes/template_element.rs b/rust/parse_ast/src/ast_nodes/template_element.rs index 3b9d385fb60..ee93394759a 100644 --- a/rust/parse_ast/src/ast_nodes/template_element.rs +++ b/rust/parse_ast/src/ast_nodes/template_element.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::{store_template_element, store_template_element_flags}; impl<'a> AstConverter<'a> { - pub fn store_template_element(&mut self, template_element: &TplElement) { + pub(crate) fn store_template_element(&mut self, template_element: &TplElement) { store_template_element!( self, span => &template_element.span, diff --git a/rust/parse_ast/src/ast_nodes/template_literal.rs b/rust/parse_ast/src/ast_nodes/template_literal.rs index 6548615a8fd..a584df67806 100644 --- a/rust/parse_ast/src/ast_nodes/template_literal.rs +++ b/rust/parse_ast/src/ast_nodes/template_literal.rs @@ -7,7 +7,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_template_literal(&mut self, template_literal: &Tpl) { + pub(crate) fn store_template_literal(&mut self, template_literal: &Tpl) { let end_position = self.add_type_and_start( &TYPE_TEMPLATE_LITERAL, &template_literal.span, diff --git a/rust/parse_ast/src/ast_nodes/this_expression.rs b/rust/parse_ast/src/ast_nodes/this_expression.rs index 37c0a8b8690..0e65f17bcb4 100644 --- a/rust/parse_ast/src/ast_nodes/this_expression.rs +++ b/rust/parse_ast/src/ast_nodes/this_expression.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_this_expression; impl<'a> AstConverter<'a> { - pub fn store_this_expression(&mut self, this_expression: &ThisExpr) { + pub(crate) fn store_this_expression(&mut self, this_expression: &ThisExpr) { store_this_expression!(self, span => this_expression.span); } } diff --git a/rust/parse_ast/src/ast_nodes/throw_statement.rs b/rust/parse_ast/src/ast_nodes/throw_statement.rs index 32fe923975a..0d154da6839 100644 --- a/rust/parse_ast/src/ast_nodes/throw_statement.rs +++ b/rust/parse_ast/src/ast_nodes/throw_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_throw_statement; impl<'a> AstConverter<'a> { - pub fn store_throw_statement(&mut self, throw_statement: &ThrowStmt) { + pub(crate) fn store_throw_statement(&mut self, throw_statement: &ThrowStmt) { store_throw_statement!( self, span => &throw_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/try_statement.rs b/rust/parse_ast/src/ast_nodes/try_statement.rs index a5985f28541..53743332781 100644 --- a/rust/parse_ast/src/ast_nodes/try_statement.rs +++ b/rust/parse_ast/src/ast_nodes/try_statement.rs @@ -7,7 +7,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_try_statement(&mut self, try_statement: &TryStmt) { + pub(crate) fn store_try_statement(&mut self, try_statement: &TryStmt) { let end_position = self.add_type_and_start( &TYPE_TRY_STATEMENT, &try_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/unary_expression.rs b/rust/parse_ast/src/ast_nodes/unary_expression.rs index 29141028576..34f5c9a8120 100644 --- a/rust/parse_ast/src/ast_nodes/unary_expression.rs +++ b/rust/parse_ast/src/ast_nodes/unary_expression.rs @@ -7,7 +7,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_unary_expression; impl<'a> AstConverter<'a> { - pub fn store_unary_expression(&mut self, unary_expression: &UnaryExpr) { + pub(crate) fn store_unary_expression(&mut self, unary_expression: &UnaryExpr) { store_unary_expression!( self, span => &unary_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/update_expression.rs b/rust/parse_ast/src/ast_nodes/update_expression.rs index cbf59a3f5cb..5886609c8bd 100644 --- a/rust/parse_ast/src/ast_nodes/update_expression.rs +++ b/rust/parse_ast/src/ast_nodes/update_expression.rs @@ -5,7 +5,7 @@ use crate::convert_ast::converter::AstConverter; use crate::{store_update_expression, store_update_expression_flags}; impl<'a> AstConverter<'a> { - pub fn store_update_expression(&mut self, update_expression: &UpdateExpr) { + pub(crate) fn store_update_expression(&mut self, update_expression: &UpdateExpr) { store_update_expression!( self, span => &update_expression.span, diff --git a/rust/parse_ast/src/ast_nodes/variable_declaration.rs b/rust/parse_ast/src/ast_nodes/variable_declaration.rs index 65108c3313a..9dc4760d8f1 100644 --- a/rust/parse_ast/src/ast_nodes/variable_declaration.rs +++ b/rust/parse_ast/src/ast_nodes/variable_declaration.rs @@ -11,7 +11,7 @@ use crate::convert_ast::converter::string_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_variable_declaration(&mut self, variable_declaration: &VariableDeclaration) { + pub(crate) fn store_variable_declaration(&mut self, variable_declaration: &VariableDeclaration) { let (kind, span, decls): (&[u8; 4], Span, &Vec) = match variable_declaration { VariableDeclaration::Var(value) => ( match value.kind { @@ -55,7 +55,7 @@ impl<'a> AstConverter<'a> { } } -pub enum VariableDeclaration<'a> { +pub(crate) enum VariableDeclaration<'a> { Var(&'a VarDecl), Using(&'a UsingDecl), } diff --git a/rust/parse_ast/src/ast_nodes/variable_declarator.rs b/rust/parse_ast/src/ast_nodes/variable_declarator.rs index d7d421197f2..317b1b29e9a 100644 --- a/rust/parse_ast/src/ast_nodes/variable_declarator.rs +++ b/rust/parse_ast/src/ast_nodes/variable_declarator.rs @@ -8,7 +8,7 @@ use crate::convert_ast::converter::ast_constants::{ use crate::convert_ast::converter::AstConverter; impl<'a> AstConverter<'a> { - pub fn store_variable_declarator(&mut self, variable_declarator: &VarDeclarator) { + pub(crate) fn store_variable_declarator(&mut self, variable_declarator: &VarDeclarator) { let end_position = self.add_type_and_start( &TYPE_VARIABLE_DECLARATOR, &variable_declarator.span, diff --git a/rust/parse_ast/src/ast_nodes/while_statement.rs b/rust/parse_ast/src/ast_nodes/while_statement.rs index d135253558c..42e00184ba9 100644 --- a/rust/parse_ast/src/ast_nodes/while_statement.rs +++ b/rust/parse_ast/src/ast_nodes/while_statement.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::store_while_statement; impl<'a> AstConverter<'a> { - pub fn store_while_statement(&mut self, while_statement: &WhileStmt) { + pub(crate) fn store_while_statement(&mut self, while_statement: &WhileStmt) { store_while_statement!( self, span => &while_statement.span, diff --git a/rust/parse_ast/src/ast_nodes/yield_expression.rs b/rust/parse_ast/src/ast_nodes/yield_expression.rs index dbd4d3f9a84..96065311331 100644 --- a/rust/parse_ast/src/ast_nodes/yield_expression.rs +++ b/rust/parse_ast/src/ast_nodes/yield_expression.rs @@ -4,7 +4,7 @@ use crate::convert_ast::converter::AstConverter; use crate::{store_yield_expression, store_yield_expression_flags}; impl<'a> AstConverter<'a> { - pub fn store_yield_expression(&mut self, yield_expression: &YieldExpr) { + pub(crate) fn store_yield_expression(&mut self, yield_expression: &YieldExpr) { store_yield_expression!( self, span => yield_expression.span, diff --git a/rust/parse_ast/src/convert_ast.rs b/rust/parse_ast/src/convert_ast.rs index e7d6154cdbf..9b9b1086064 100644 --- a/rust/parse_ast/src/convert_ast.rs +++ b/rust/parse_ast/src/convert_ast.rs @@ -1,2 +1,2 @@ -pub mod annotations; -pub mod converter; +pub(crate) mod annotations; +pub(crate) mod converter; diff --git a/rust/parse_ast/src/convert_ast/annotations.rs b/rust/parse_ast/src/convert_ast/annotations.rs index a5c61b835af..0ac6f31164a 100644 --- a/rust/parse_ast/src/convert_ast/annotations.rs +++ b/rust/parse_ast/src/convert_ast/annotations.rs @@ -4,12 +4,12 @@ use swc_common::comments::{Comment, Comments}; use swc_common::BytePos; #[derive(Default)] -pub struct SequentialComments { +pub(crate) struct SequentialComments { annotations: RefCell>, } impl SequentialComments { - pub fn add_comment(&self, comment: Comment) { + pub(crate) fn add_comment(&self, comment: Comment) { if comment.text.starts_with('#') && comment.text.contains("sourceMappingURL=") { self.annotations.borrow_mut().push(AnnotationWithType { comment, @@ -55,7 +55,7 @@ impl SequentialComments { }); } - pub fn take_annotations(self) -> Vec { + pub(crate) fn take_annotations(self) -> Vec { self.annotations.take() } } @@ -127,19 +127,19 @@ impl Comments for SequentialComments { } #[derive(Debug)] -pub struct AnnotationWithType { - pub comment: Comment, - pub kind: CommentKind, +pub(crate) struct AnnotationWithType { + pub(crate) comment: Comment, + pub(crate) kind: CommentKind, } #[derive(Clone, Debug)] -pub enum CommentKind { +pub(crate) enum CommentKind { Annotation(AnnotationKind), Comment, } #[derive(Clone, PartialEq, Debug)] -pub enum AnnotationKind { +pub(crate) enum AnnotationKind { Pure, NoSideEffects, SourceMappingUrl, diff --git a/rust/parse_ast/src/convert_ast/converter.rs b/rust/parse_ast/src/convert_ast/converter.rs index b5d55d15505..c736feb1349 100644 --- a/rust/parse_ast/src/convert_ast/converter.rs +++ b/rust/parse_ast/src/convert_ast/converter.rs @@ -1,7 +1,8 @@ use swc_common::Span; use swc_ecma_ast::{ AssignTarget, AssignTargetPat, CallExpr, Callee, ClassMember, Decl, ExportSpecifier, Expr, - ExprOrSpread, ForHead, ImportSpecifier, Lit, ModuleDecl, ModuleExportName, ModuleItem, + ExprOrSpread, ForHead, ImportSpecifier, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, + JSXElementChild, JSXElementName, JSXObject, Lit, ModuleDecl, ModuleExportName, ModuleItem, NamedExport, ObjectPatProp, OptChainBase, ParenExpr, Pat, Program, PropName, PropOrSpread, SimpleAssignTarget, Stmt, VarDeclOrExpr, }; @@ -20,20 +21,20 @@ use crate::convert_ast::converter::utf16_positions::{ }; pub(crate) mod analyze_code; -pub mod string_constants; +pub(crate) mod string_constants; mod utf16_positions; -pub mod ast_constants; +pub(crate) mod ast_constants; mod ast_macros; -pub struct AstConverter<'a> { - pub buffer: Vec, - pub code: &'a [u8], - pub index_converter: Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a>, +pub(crate) struct AstConverter<'a> { + pub(crate) buffer: Vec, + pub(crate) code: &'a [u8], + pub(crate) index_converter: Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a>, } impl<'a> AstConverter<'a> { - pub fn new(code: &'a str, annotations: &'a [AnnotationWithType]) -> Self { + pub(crate) fn new(code: &'a str, annotations: &'a [AnnotationWithType]) -> Self { Self { // This is just a wild guess and should be revisited from time to time buffer: Vec::with_capacity(20 * code.len()), @@ -42,14 +43,14 @@ impl<'a> AstConverter<'a> { } } - pub fn convert_ast_to_buffer(mut self, node: &Program) -> Vec { + pub(crate) fn convert_ast_to_buffer(mut self, node: &Program) -> Vec { self.convert_program(node); self.buffer.shrink_to_fit(); self.buffer } // === helpers - pub fn add_type_and_start( + pub(crate) fn add_type_and_start( &mut self, node_type: &[u8; 4], span: &Span, @@ -89,7 +90,7 @@ impl<'a> AstConverter<'a> { end_position } - pub fn add_end(&mut self, end_position: usize, span: &Span) { + pub(crate) fn add_end(&mut self, end_position: usize, span: &Span) { self.buffer[end_position..end_position + 4].copy_from_slice( &self .index_converter @@ -103,7 +104,7 @@ impl<'a> AstConverter<'a> { .copy_from_slice(&self.index_converter.convert(end, false).to_ne_bytes()); } - pub fn convert_item_list( + pub(crate) fn convert_item_list( &mut self, item_list: &[T], reference_position: usize, @@ -135,7 +136,7 @@ impl<'a> AstConverter<'a> { } } - pub fn convert_item_list_with_state( + pub(crate) fn convert_item_list_with_state( &mut self, item_list: &[T], state: &mut S, @@ -174,14 +175,14 @@ impl<'a> AstConverter<'a> { convert_string(&mut self.buffer, string); } - pub fn update_reference_position(&mut self, reference_position: usize) { + pub(crate) fn update_reference_position(&mut self, reference_position: usize) { let insert_position = (self.buffer.len() as u32) >> 2; self.buffer[reference_position..reference_position + 4] .copy_from_slice(&insert_position.to_ne_bytes()); } // === shared enums - pub fn convert_call_expression( + pub(crate) fn convert_call_expression( &mut self, call_expression: &CallExpr, is_optional: bool, @@ -275,7 +276,7 @@ impl<'a> AstConverter<'a> { } } - pub fn convert_expression(&mut self, expression: &Expr) { + pub(crate) fn convert_expression(&mut self, expression: &Expr) { match expression { Expr::Array(array_literal) => { self.store_array_expression(array_literal); @@ -362,8 +363,12 @@ impl<'a> AstConverter<'a> { Expr::JSXMember(_) => unimplemented!("Cannot convert Expr::JSXMember"), Expr::JSXNamespacedName(_) => unimplemented!("Cannot convert Expr::JSXNamespacedName"), Expr::JSXEmpty(_) => unimplemented!("Cannot convert Expr::JSXEmpty"), - Expr::JSXElement(_) => unimplemented!("Cannot convert Expr::JSXElement"), - Expr::JSXFragment(_) => unimplemented!("Cannot convert Expr::JSXFragment"), + Expr::JSXElement(jsx_element) => { + self.store_jsx_element(jsx_element); + } + Expr::JSXFragment(jsx_fragment) => { + self.store_jsx_fragment(jsx_fragment); + } Expr::TsTypeAssertion(_) => unimplemented!("Cannot convert Expr::TsTypeAssertion"), Expr::TsConstAssertion(_) => unimplemented!("Cannot convert Expr::TsConstAssertion"), Expr::TsNonNull(_) => unimplemented!("Cannot convert Expr::TsNonNull"), @@ -374,7 +379,7 @@ impl<'a> AstConverter<'a> { } } - pub fn convert_expression_or_spread(&mut self, expression_or_spread: &ExprOrSpread) { + pub(crate) fn convert_expression_or_spread(&mut self, expression_or_spread: &ExprOrSpread) { match expression_or_spread.spread { Some(spread_span) => self.store_spread_element(&spread_span, &expression_or_spread.expr), None => { @@ -411,6 +416,88 @@ impl<'a> AstConverter<'a> { } } + pub(crate) fn convert_jsx_attribute_name(&mut self, jsx_attribute_name: &JSXAttrName) { + match jsx_attribute_name { + JSXAttrName::Ident(identifier) => { + self.store_jsx_identifier(&identifier.span, &identifier.sym); + } + JSXAttrName::JSXNamespacedName(jsx_namespaced_name) => { + self.store_jsx_namespaced_name(jsx_namespaced_name); + } + } + } + + pub(crate) fn convert_jsx_attribute_or_spread( + &mut self, + jsx_attribute_or_spread: &JSXAttrOrSpread, + previous_element_end: u32, + ) { + match jsx_attribute_or_spread { + JSXAttrOrSpread::JSXAttr(jsx_attribute) => { + self.store_jsx_attribute(jsx_attribute); + } + JSXAttrOrSpread::SpreadElement(spread_element) => { + self.store_jsx_spread_attribute(spread_element, previous_element_end); + } + } + } + + pub(crate) fn convert_jsx_attribute_value(&mut self, jsx_attribute_value: &JSXAttrValue) { + match jsx_attribute_value { + JSXAttrValue::Lit(literal) => self.convert_literal(literal), + JSXAttrValue::JSXExprContainer(expression_container) => { + self.store_jsx_expression_container(expression_container) + } + JSXAttrValue::JSXElement(jsx_element) => self.store_jsx_element(jsx_element), + JSXAttrValue::JSXFragment(jsx_fragment) => self.store_jsx_fragment(jsx_fragment), + } + } + + pub(crate) fn convert_jsx_element_child(&mut self, jsx_element_child: &JSXElementChild) { + match jsx_element_child { + JSXElementChild::JSXText(jsx_text) => { + self.store_jsx_text(jsx_text); + } + JSXElementChild::JSXExprContainer(jsx_expr_container) => { + self.store_jsx_expression_container(jsx_expr_container); + } + JSXElementChild::JSXSpreadChild(jsx_spread_child) => { + self.store_jsx_spread_child(jsx_spread_child); + } + JSXElementChild::JSXFragment(jsx_fragment) => { + self.store_jsx_fragment(jsx_fragment); + } + JSXElementChild::JSXElement(jsx_element) => { + self.store_jsx_element(jsx_element); + } + } + } + + pub(crate) fn convert_jsx_element_name(&mut self, jsx_element_name: &JSXElementName) { + match jsx_element_name { + JSXElementName::Ident(identifier) => { + self.store_jsx_identifier(&identifier.span, &identifier.sym) + } + JSXElementName::JSXMemberExpr(jsx_member_expression) => { + self.store_jsx_member_expression(jsx_member_expression); + } + JSXElementName::JSXNamespacedName(_jsx_namespaced_name) => { + unimplemented!("JSXElementName::JSXNamespacedName") + } + } + } + + pub(crate) fn convert_jsx_object(&mut self, jsx_object: &JSXObject) { + match jsx_object { + JSXObject::JSXMemberExpr(jsx_member_expression) => { + self.store_jsx_member_expression(jsx_member_expression); + } + JSXObject::Ident(identifier) => { + self.store_jsx_identifier(&identifier.span, &identifier.sym); + } + } + } + fn convert_literal(&mut self, literal: &Lit) { match literal { Lit::BigInt(bigint_literal) => self.store_literal_bigint(bigint_literal), @@ -429,7 +516,7 @@ impl<'a> AstConverter<'a> { Lit::Str(string_literal) => { self.store_literal_string(string_literal); } - Lit::JSXText(_) => unimplemented!("Lit::JSXText"), + Lit::JSXText(_) => unimplemented!("Lit::JsxText"), } } @@ -515,7 +602,7 @@ impl<'a> AstConverter<'a> { self.convert_expression(&parenthesized_expression.expr); } - pub fn convert_pattern(&mut self, pattern: &Pat) { + pub(crate) fn convert_pattern(&mut self, pattern: &Pat) { match pattern { Pat::Array(array_pattern) => { self.store_array_pattern(array_pattern); @@ -537,7 +624,7 @@ impl<'a> AstConverter<'a> { } } - pub fn convert_pattern_or_expression(&mut self, pattern_or_expression: &AssignTarget) { + pub(crate) fn convert_pattern_or_expression(&mut self, pattern_or_expression: &AssignTarget) { match pattern_or_expression { AssignTarget::Pat(assignment_target_pattern) => { self.convert_assignment_target_pattern(assignment_target_pattern); @@ -617,7 +704,7 @@ impl<'a> AstConverter<'a> { } } - pub fn convert_statement(&mut self, statement: &Stmt) { + pub(crate) fn convert_statement(&mut self, statement: &Stmt) { match statement { Stmt::Break(break_statement) => self.store_break_statement(break_statement), Stmt::Block(block_statement) => self.store_block_statement(block_statement, false), @@ -656,7 +743,7 @@ impl<'a> AstConverter<'a> { } } -pub fn convert_annotation(buffer: &mut Vec, annotation: &ConvertedAnnotation) { +pub(crate) fn convert_annotation(buffer: &mut Vec, annotation: &ConvertedAnnotation) { // start buffer.extend_from_slice(&annotation.start.to_ne_bytes()); // end @@ -669,7 +756,7 @@ pub fn convert_annotation(buffer: &mut Vec, annotation: &ConvertedAnnotation }); } -pub fn convert_string(buffer: &mut Vec, string: &str) { +pub(crate) fn convert_string(buffer: &mut Vec, string: &str) { let length = string.len(); let additional_length = ((length + 3) & !3) - length; buffer.extend_from_slice(&(length as u32).to_ne_bytes()); @@ -677,7 +764,7 @@ pub fn convert_string(buffer: &mut Vec, string: &str) { buffer.resize(buffer.len() + additional_length, 0); } -pub fn update_reference_position(buffer: &mut [u8], reference_position: usize) { +pub(crate) fn update_reference_position(buffer: &mut [u8], reference_position: usize) { let insert_position = (buffer.len() as u32) >> 2; buffer[reference_position..reference_position + 4] .copy_from_slice(&insert_position.to_ne_bytes()); diff --git a/rust/parse_ast/src/convert_ast/converter/analyze_code.rs b/rust/parse_ast/src/convert_ast/converter/analyze_code.rs index d2fb985df23..2aa770a98d9 100644 --- a/rust/parse_ast/src/convert_ast/converter/analyze_code.rs +++ b/rust/parse_ast/src/convert_ast/converter/analyze_code.rs @@ -1,4 +1,8 @@ -pub fn find_first_occurrence_outside_comment(code: &[u8], search_byte: u8, start: u32) -> u32 { +pub(crate) fn find_first_occurrence_outside_comment( + code: &[u8], + search_byte: u8, + start: u32, +) -> u32 { let mut search_pos = start as usize; let mut comment_type = CommentType::None; loop { diff --git a/rust/parse_ast/src/convert_ast/converter/ast_constants.rs b/rust/parse_ast/src/convert_ast/converter/ast_constants.rs index 72f6335f85f..add6ddf81e8 100644 --- a/rust/parse_ast/src/convert_ast/converter/ast_constants.rs +++ b/rust/parse_ast/src/convert_ast/converter/ast_constants.rs @@ -24,20 +24,26 @@ pub const TYPE_IDENTIFIER: [u8; 4] = 34u32.to_ne_bytes(); pub const TYPE_IMPORT_ATTRIBUTE: [u8; 4] = 36u32.to_ne_bytes(); pub const TYPE_IMPORT_DECLARATION: [u8; 4] = 37u32.to_ne_bytes(); pub const TYPE_IMPORT_EXPRESSION: [u8; 4] = 39u32.to_ne_bytes(); -pub const TYPE_LOGICAL_EXPRESSION: [u8; 4] = 49u32.to_ne_bytes(); -pub const TYPE_MEMBER_EXPRESSION: [u8; 4] = 50u32.to_ne_bytes(); -pub const TYPE_META_PROPERTY: [u8; 4] = 51u32.to_ne_bytes(); -pub const TYPE_METHOD_DEFINITION: [u8; 4] = 52u32.to_ne_bytes(); -pub const TYPE_NEW_EXPRESSION: [u8; 4] = 53u32.to_ne_bytes(); -pub const TYPE_PROGRAM: [u8; 4] = 57u32.to_ne_bytes(); -pub const TYPE_PROPERTY: [u8; 4] = 58u32.to_ne_bytes(); -pub const TYPE_PROPERTY_DEFINITION: [u8; 4] = 59u32.to_ne_bytes(); -pub const TYPE_REST_ELEMENT: [u8; 4] = 60u32.to_ne_bytes(); -pub const TYPE_SPREAD_ELEMENT: [u8; 4] = 63u32.to_ne_bytes(); -pub const TYPE_TEMPLATE_LITERAL: [u8; 4] = 70u32.to_ne_bytes(); -pub const TYPE_TRY_STATEMENT: [u8; 4] = 73u32.to_ne_bytes(); -pub const TYPE_VARIABLE_DECLARATION: [u8; 4] = 76u32.to_ne_bytes(); -pub const TYPE_VARIABLE_DECLARATOR: [u8; 4] = 77u32.to_ne_bytes(); +pub const TYPE_JSX_EMPTY_EXPRESSION: [u8; 4] = 46u32.to_ne_bytes(); +pub const TYPE_JSX_EXPRESSION_CONTAINER: [u8; 4] = 47u32.to_ne_bytes(); +pub const TYPE_JSX_MEMBER_EXPRESSION: [u8; 4] = 50u32.to_ne_bytes(); +pub const TYPE_JSX_NAMESPACED_NAME: [u8; 4] = 51u32.to_ne_bytes(); +pub const TYPE_JSX_OPENING_ELEMENT: [u8; 4] = 52u32.to_ne_bytes(); +pub const TYPE_JSX_SPREAD_ATTRIBUTE: [u8; 4] = 54u32.to_ne_bytes(); +pub const TYPE_LOGICAL_EXPRESSION: [u8; 4] = 64u32.to_ne_bytes(); +pub const TYPE_MEMBER_EXPRESSION: [u8; 4] = 65u32.to_ne_bytes(); +pub const TYPE_META_PROPERTY: [u8; 4] = 66u32.to_ne_bytes(); +pub const TYPE_METHOD_DEFINITION: [u8; 4] = 67u32.to_ne_bytes(); +pub const TYPE_NEW_EXPRESSION: [u8; 4] = 68u32.to_ne_bytes(); +pub const TYPE_PROGRAM: [u8; 4] = 72u32.to_ne_bytes(); +pub const TYPE_PROPERTY: [u8; 4] = 73u32.to_ne_bytes(); +pub const TYPE_PROPERTY_DEFINITION: [u8; 4] = 74u32.to_ne_bytes(); +pub const TYPE_REST_ELEMENT: [u8; 4] = 75u32.to_ne_bytes(); +pub const TYPE_SPREAD_ELEMENT: [u8; 4] = 78u32.to_ne_bytes(); +pub const TYPE_TEMPLATE_LITERAL: [u8; 4] = 85u32.to_ne_bytes(); +pub const TYPE_TRY_STATEMENT: [u8; 4] = 88u32.to_ne_bytes(); +pub const TYPE_VARIABLE_DECLARATION: [u8; 4] = 91u32.to_ne_bytes(); +pub const TYPE_VARIABLE_DECLARATOR: [u8; 4] = 92u32.to_ne_bytes(); pub const PANIC_ERROR_RESERVED_BYTES: usize = 8; pub const PANIC_ERROR_MESSAGE_OFFSET: usize = 4; @@ -125,6 +131,26 @@ pub const IMPORT_EXPRESSION_RESERVED_BYTES: usize = 12; pub const IMPORT_EXPRESSION_SOURCE_OFFSET: usize = 4; pub const IMPORT_EXPRESSION_OPTIONS_OFFSET: usize = 8; +pub const JSX_EMPTY_EXPRESSION_RESERVED_BYTES: usize = 4; + +pub const JSX_EXPRESSION_CONTAINER_RESERVED_BYTES: usize = 8; +pub const JSX_EXPRESSION_CONTAINER_EXPRESSION_OFFSET: usize = 4; + +pub const JSX_MEMBER_EXPRESSION_RESERVED_BYTES: usize = 12; +pub const JSX_MEMBER_EXPRESSION_OBJECT_OFFSET: usize = 4; +pub const JSX_MEMBER_EXPRESSION_PROPERTY_OFFSET: usize = 8; + +pub const JSX_NAMESPACED_NAME_RESERVED_BYTES: usize = 12; +pub const JSX_NAMESPACED_NAME_NAMESPACE_OFFSET: usize = 4; +pub const JSX_NAMESPACED_NAME_NAME_OFFSET: usize = 8; + +pub const JSX_OPENING_ELEMENT_RESERVED_BYTES: usize = 16; +pub const JSX_OPENING_ELEMENT_NAME_OFFSET: usize = 8; +pub const JSX_OPENING_ELEMENT_ATTRIBUTES_OFFSET: usize = 12; + +pub const JSX_SPREAD_ATTRIBUTE_RESERVED_BYTES: usize = 8; +pub const JSX_SPREAD_ATTRIBUTE_ARGUMENT_OFFSET: usize = 4; + pub const MEMBER_EXPRESSION_RESERVED_BYTES: usize = 16; pub const MEMBER_EXPRESSION_OBJECT_OFFSET: usize = 8; pub const MEMBER_EXPRESSION_PROPERTY_OFFSET: usize = 12; diff --git a/rust/parse_ast/src/convert_ast/converter/ast_macros.rs b/rust/parse_ast/src/convert_ast/converter/ast_macros.rs index 9270789b436..84fff18bf9a 100644 --- a/rust/parse_ast/src/convert_ast/converter/ast_macros.rs +++ b/rust/parse_ast/src/convert_ast/converter/ast_macros.rs @@ -315,11 +315,145 @@ macro_rules! store_import_specifier { }; } +#[macro_export] +macro_rules! store_jsx_attribute { + ($self:expr, span => $span:expr, name => [$name_value:expr, $name_converter:ident], value => [$value_value:expr, $value_converter:ident]) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&42u32.to_ne_bytes(), &$span, 12, false); + // name + $self.update_reference_position(end_position + 4); + $self.$name_converter(&$name_value); + // value + if let Some(value) = $value_value.as_ref() { + $self.update_reference_position(end_position + 8); + $self.$value_converter(value); + } + // end + $self.add_end(end_position, &$span); + }; +} + +#[macro_export] +macro_rules! store_jsx_closing_element { + ($self:expr, span => $span:expr, name => [$name_value:expr, $name_converter:ident]) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&43u32.to_ne_bytes(), &$span, 8, false); + // name + $self.update_reference_position(end_position + 4); + $self.$name_converter(&$name_value); + // end + $self.add_end(end_position, &$span); + }; +} + +#[macro_export] +macro_rules! store_jsx_closing_fragment { + ($self:expr, span => $span:expr) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&44u32.to_ne_bytes(), &$span, 4, false); + // end + $self.add_end(end_position, &$span); + }; +} + +#[macro_export] +macro_rules! store_jsx_element { + ($self:expr, span => $span:expr, openingElement => [$openingElement_value:expr, $openingElement_converter:ident], children => [$children_value:expr, $children_converter:ident], closingElement => [$closingElement_value:expr, $closingElement_converter:ident]) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&45u32.to_ne_bytes(), &$span, 16, false); + // openingElement + $self.update_reference_position(end_position + 4); + $self.$openingElement_converter(&$openingElement_value); + // children + $self.convert_item_list(&$children_value, end_position + 8, |ast_converter, node| { + ast_converter.$children_converter(node); + true + }); + // closingElement + if let Some(value) = $closingElement_value.as_ref() { + $self.update_reference_position(end_position + 12); + $self.$closingElement_converter(value); + } + // end + $self.add_end(end_position, &$span); + }; +} + +#[macro_export] +macro_rules! store_jsx_fragment { + ($self:expr, span => $span:expr, openingFragment => [$openingFragment_value:expr, $openingFragment_converter:ident], children => [$children_value:expr, $children_converter:ident], closingFragment => [$closingFragment_value:expr, $closingFragment_converter:ident]) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&48u32.to_ne_bytes(), &$span, 16, false); + // openingFragment + $self.update_reference_position(end_position + 4); + $self.$openingFragment_converter(&$openingFragment_value); + // children + $self.convert_item_list(&$children_value, end_position + 8, |ast_converter, node| { + ast_converter.$children_converter(node); + true + }); + // closingFragment + $self.update_reference_position(end_position + 12); + $self.$closingFragment_converter(&$closingFragment_value); + // end + $self.add_end(end_position, &$span); + }; +} + +#[macro_export] +macro_rules! store_jsx_identifier { + ($self:expr, span => $span:expr, name => $name_value:expr) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&49u32.to_ne_bytes(), &$span, 8, false); + // name + $self.convert_string($name_value, end_position + 4); + // end + $self.add_end(end_position, &$span); + }; +} + +#[macro_export] +macro_rules! store_jsx_opening_fragment { + ($self:expr, span => $span:expr) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&53u32.to_ne_bytes(), &$span, 4, false); + // end + $self.add_end(end_position, &$span); + }; +} + +#[macro_export] +macro_rules! store_jsx_spread_child { + ($self:expr, span => $span:expr, expression => [$expression_value:expr, $expression_converter:ident]) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&55u32.to_ne_bytes(), &$span, 8, false); + // expression + $self.update_reference_position(end_position + 4); + $self.$expression_converter(&$expression_value); + // end + $self.add_end(end_position, &$span); + }; +} + +#[macro_export] +macro_rules! store_jsx_text { + ($self:expr, span => $span:expr, value => $value_value:expr, raw => $raw_value:expr) => { + let _: &mut AstConverter = $self; + let end_position = $self.add_type_and_start(&56u32.to_ne_bytes(), &$span, 12, false); + // value + $self.convert_string($value_value, end_position + 4); + // raw + $self.convert_string($raw_value, end_position + 8); + // end + $self.add_end(end_position, &$span); + }; +} + #[macro_export] macro_rules! store_labeled_statement { ($self:expr, span => $span:expr, label => [$label_value:expr, $label_converter:ident], body => [$body_value:expr, $body_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&42u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&57u32.to_ne_bytes(), &$span, 12, false); // label $self.update_reference_position(end_position + 4); $self.$label_converter(&$label_value); @@ -335,7 +469,7 @@ macro_rules! store_labeled_statement { macro_rules! store_literal_big_int { ($self:expr, span => $span:expr, bigint => $bigint_value:expr, raw => $raw_value:expr) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&43u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&58u32.to_ne_bytes(), &$span, 12, false); // bigint $self.convert_string($bigint_value, end_position + 4); // raw @@ -350,7 +484,7 @@ macro_rules! store_literal_boolean { ($self:expr, span => $span:expr, value => $value_value:expr) => { let _: &mut AstConverter = $self; let end_position = $self.add_type_and_start( - &44u32.to_ne_bytes(), + &59u32.to_ne_bytes(), &$span, 8, false, @@ -366,7 +500,7 @@ macro_rules! store_literal_boolean { macro_rules! store_literal_null { ($self:expr, span => $span:expr) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&45u32.to_ne_bytes(), &$span, 4, false); + let end_position = $self.add_type_and_start(&60u32.to_ne_bytes(), &$span, 4, false); // end $self.add_end(end_position, &$span); }; @@ -376,7 +510,7 @@ macro_rules! store_literal_null { macro_rules! store_literal_number { ($self:expr, span => $span:expr, raw => $raw_value:expr, value => $value_value:expr) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&46u32.to_ne_bytes(), &$span, 16, false); + let end_position = $self.add_type_and_start(&61u32.to_ne_bytes(), &$span, 16, false); // raw if let Some(value) = $raw_value.as_ref() { $self.convert_string(value, end_position + 4); @@ -393,7 +527,7 @@ macro_rules! store_literal_number { macro_rules! store_literal_reg_exp { ($self:expr, span => $span:expr, flags => $flags_value:expr, pattern => $pattern_value:expr) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&47u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&62u32.to_ne_bytes(), &$span, 12, false); // flags $self.convert_string($flags_value, end_position + 4); // pattern @@ -407,7 +541,7 @@ macro_rules! store_literal_reg_exp { macro_rules! store_literal_string { ($self:expr, span => $span:expr, value => $value_value:expr, raw => $raw_value:expr) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&48u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&63u32.to_ne_bytes(), &$span, 12, false); // value $self.convert_string($value_value, end_position + 4); // raw @@ -423,7 +557,7 @@ macro_rules! store_literal_string { macro_rules! store_object_expression { ($self:expr, span => $span:expr, properties => [$properties_value:expr, $properties_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&54u32.to_ne_bytes(), &$span, 8, false); + let end_position = $self.add_type_and_start(&69u32.to_ne_bytes(), &$span, 8, false); // properties $self.convert_item_list( &$properties_value, @@ -442,7 +576,7 @@ macro_rules! store_object_expression { macro_rules! store_object_pattern { ($self:expr, span => $span:expr, properties => [$properties_value:expr, $properties_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&55u32.to_ne_bytes(), &$span, 8, false); + let end_position = $self.add_type_and_start(&70u32.to_ne_bytes(), &$span, 8, false); // properties $self.convert_item_list( &$properties_value, @@ -461,7 +595,7 @@ macro_rules! store_object_pattern { macro_rules! store_private_identifier { ($self:expr, span => $span:expr, name => $name_value:expr) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&56u32.to_ne_bytes(), &$span, 8, false); + let end_position = $self.add_type_and_start(&71u32.to_ne_bytes(), &$span, 8, false); // name $self.convert_string($name_value, end_position + 4); // end @@ -473,7 +607,7 @@ macro_rules! store_private_identifier { macro_rules! store_return_statement { ($self:expr, span => $span:expr, argument => [$argument_value:expr, $argument_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&61u32.to_ne_bytes(), &$span, 8, false); + let end_position = $self.add_type_and_start(&76u32.to_ne_bytes(), &$span, 8, false); // argument if let Some(value) = $argument_value.as_ref() { $self.update_reference_position(end_position + 4); @@ -488,7 +622,7 @@ macro_rules! store_return_statement { macro_rules! store_sequence_expression { ($self:expr, span => $span:expr, expressions => [$expressions_value:expr, $expressions_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&62u32.to_ne_bytes(), &$span, 8, false); + let end_position = $self.add_type_and_start(&77u32.to_ne_bytes(), &$span, 8, false); // expressions $self.convert_item_list( &$expressions_value, @@ -507,7 +641,7 @@ macro_rules! store_sequence_expression { macro_rules! store_static_block { ($self:expr, span => $span:expr, body => [$body_value:expr, $body_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&64u32.to_ne_bytes(), &$span, 8, false); + let end_position = $self.add_type_and_start(&79u32.to_ne_bytes(), &$span, 8, false); // body $self.convert_item_list(&$body_value, end_position + 4, |ast_converter, node| { ast_converter.$body_converter(node); @@ -522,7 +656,7 @@ macro_rules! store_static_block { macro_rules! store_super_element { ($self:expr, span => $span:expr) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&65u32.to_ne_bytes(), &$span, 4, false); + let end_position = $self.add_type_and_start(&80u32.to_ne_bytes(), &$span, 4, false); // end $self.add_end(end_position, &$span); }; @@ -532,7 +666,7 @@ macro_rules! store_super_element { macro_rules! store_switch_case { ($self:expr, span => $span:expr, test => [$test_value:expr, $test_converter:ident], consequent => [$consequent_value:expr, $consequent_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&66u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&81u32.to_ne_bytes(), &$span, 12, false); // test if let Some(value) = $test_value.as_ref() { $self.update_reference_position(end_position + 4); @@ -556,7 +690,7 @@ macro_rules! store_switch_case { macro_rules! store_switch_statement { ($self:expr, span => $span:expr, discriminant => [$discriminant_value:expr, $discriminant_converter:ident], cases => [$cases_value:expr, $cases_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&67u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&82u32.to_ne_bytes(), &$span, 12, false); // discriminant $self.update_reference_position(end_position + 4); $self.$discriminant_converter(&$discriminant_value); @@ -574,7 +708,7 @@ macro_rules! store_switch_statement { macro_rules! store_tagged_template_expression { ($self:expr, span => $span:expr, tag => [$tag_value:expr, $tag_converter:ident], quasi => [$quasi_value:expr, $quasi_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&68u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&83u32.to_ne_bytes(), &$span, 12, false); // tag $self.update_reference_position(end_position + 4); $self.$tag_converter(&$tag_value); @@ -591,7 +725,7 @@ macro_rules! store_template_element { ($self:expr, span => $span:expr, tail => $tail_value:expr, cooked => $cooked_value:expr, raw => $raw_value:expr) => { let _: &mut AstConverter = $self; let end_position = $self.add_type_and_start( - &69u32.to_ne_bytes(), + &84u32.to_ne_bytes(), &$span, 16, false, @@ -613,7 +747,7 @@ macro_rules! store_template_element { macro_rules! store_this_expression { ($self:expr, span => $span:expr) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&71u32.to_ne_bytes(), &$span, 4, false); + let end_position = $self.add_type_and_start(&86u32.to_ne_bytes(), &$span, 4, false); // end $self.add_end(end_position, &$span); }; @@ -623,7 +757,7 @@ macro_rules! store_this_expression { macro_rules! store_throw_statement { ($self:expr, span => $span:expr, argument => [$argument_value:expr, $argument_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&72u32.to_ne_bytes(), &$span, 8, false); + let end_position = $self.add_type_and_start(&87u32.to_ne_bytes(), &$span, 8, false); // argument $self.update_reference_position(end_position + 4); $self.$argument_converter(&$argument_value); @@ -636,7 +770,7 @@ macro_rules! store_throw_statement { macro_rules! store_unary_expression { ($self:expr, span => $span:expr, operator => $operator_value:expr, argument => [$argument_value:expr, $argument_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&74u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&89u32.to_ne_bytes(), &$span, 12, false); // operator let operator_position = end_position + 4; $self.buffer[operator_position..operator_position + 4].copy_from_slice($operator_value); @@ -653,7 +787,7 @@ macro_rules! store_update_expression { ($self:expr, span => $span:expr, prefix => $prefix_value:expr, operator => $operator_value:expr, argument => [$argument_value:expr, $argument_converter:ident]) => { let _: &mut AstConverter = $self; let end_position = $self.add_type_and_start( - &75u32.to_ne_bytes(), + &90u32.to_ne_bytes(), &$span, 16, false, @@ -675,7 +809,7 @@ macro_rules! store_update_expression { macro_rules! store_while_statement { ($self:expr, span => $span:expr, test => [$test_value:expr, $test_converter:ident], body => [$body_value:expr, $body_converter:ident]) => { let _: &mut AstConverter = $self; - let end_position = $self.add_type_and_start(&78u32.to_ne_bytes(), &$span, 12, false); + let end_position = $self.add_type_and_start(&93u32.to_ne_bytes(), &$span, 12, false); // test $self.update_reference_position(end_position + 4); $self.$test_converter(&$test_value); @@ -692,7 +826,7 @@ macro_rules! store_yield_expression { ($self:expr, span => $span:expr, delegate => $delegate_value:expr, argument => [$argument_value:expr, $argument_converter:ident]) => { let _: &mut AstConverter = $self; let end_position = $self.add_type_and_start( - &79u32.to_ne_bytes(), + &94u32.to_ne_bytes(), &$span, 12, false, @@ -774,6 +908,20 @@ macro_rules! store_function_declaration_flags { }; } +#[macro_export] +macro_rules! store_jsx_opening_element_flags { + ($self:expr, $end_position:expr, selfClosing => $selfClosing_value:expr) => { + let _: &mut AstConverter = $self; + let _: usize = $end_position; + let mut flags = 0u32; + if $selfClosing_value { + flags |= 1; + } + let flags_position = $end_position + 4; + $self.buffer[flags_position..flags_position + 4].copy_from_slice(&flags.to_ne_bytes()); + }; +} + #[macro_export] macro_rules! store_literal_boolean_flags { ($self:expr, $end_position:expr, value => $value_value:expr) => { diff --git a/rust/parse_ast/src/convert_ast/converter/utf16_positions.rs b/rust/parse_ast/src/convert_ast/converter/utf16_positions.rs index 0d963b523c0..7dccfe05e52 100644 --- a/rust/parse_ast/src/convert_ast/converter/utf16_positions.rs +++ b/rust/parse_ast/src/convert_ast/converter/utf16_positions.rs @@ -4,7 +4,7 @@ use std::str::Chars; use crate::convert_ast::annotations::CommentKind::Annotation; use crate::convert_ast::annotations::{AnnotationKind, AnnotationWithType}; -pub struct Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a> { +pub(crate) struct Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a> { current_utf8_index: u32, current_utf16_index: u32, character_iterator: Chars<'a>, @@ -17,14 +17,14 @@ pub struct Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a> { } #[derive(Debug)] -pub struct ConvertedAnnotation { - pub start: u32, - pub end: u32, - pub kind: AnnotationKind, +pub(crate) struct ConvertedAnnotation { + pub(crate) start: u32, + pub(crate) end: u32, + pub(crate) kind: AnnotationKind, } impl<'a> Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a> { - pub fn new(code: &'a str, annotations: &'a [AnnotationWithType]) -> Self { + pub(crate) fn new(code: &'a str, annotations: &'a [AnnotationWithType]) -> Self { let mut annotation_iterator = annotations.iter(); let current_annotation = annotation_iterator.next(); Self { @@ -62,7 +62,7 @@ impl<'a> Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a> { /// non-whitespace from the annotation, `keep_annotations_for_next` will /// prevent annotations from being invalidated when the next position is /// converted. - pub fn convert(&mut self, utf8_index: u32, keep_annotations_for_next: bool) -> u32 { + pub(crate) fn convert(&mut self, utf8_index: u32, keep_annotations_for_next: bool) -> u32 { if self.current_utf8_index > utf8_index { panic!( "Cannot convert positions backwards: {} < {}", @@ -108,7 +108,10 @@ impl<'a> Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a> { self.current_utf16_index } - pub fn take_collected_annotations(&mut self, kind: AnnotationKind) -> Vec { + pub(crate) fn take_collected_annotations( + &mut self, + kind: AnnotationKind, + ) -> Vec { let mut relevant_annotations = Vec::new(); for annotation in self.collected_annotations.drain(..) { if annotation.kind == kind { @@ -120,18 +123,18 @@ impl<'a> Utf8ToUtf16ByteIndexConverterAndAnnotationHandler<'a> { relevant_annotations } - pub fn add_collected_annotations(&mut self, annotations: Vec) { + pub(crate) fn add_collected_annotations(&mut self, annotations: Vec) { self.collected_annotations.extend(annotations); self.keep_annotations = true; } - pub fn invalidate_collected_annotations(&mut self) { + pub(crate) fn invalidate_collected_annotations(&mut self) { self .invalid_annotations .append(&mut self.collected_annotations); } - pub fn take_invalid_annotations(&mut self) -> Vec { + pub(crate) fn take_invalid_annotations(&mut self) -> Vec { std::mem::take(&mut self.invalid_annotations) } } diff --git a/rust/parse_ast/src/error_emit.rs b/rust/parse_ast/src/error_emit.rs index 63ad66da21f..3a738704cd9 100644 --- a/rust/parse_ast/src/error_emit.rs +++ b/rust/parse_ast/src/error_emit.rs @@ -24,7 +24,7 @@ impl Write for Writer { } } -pub struct ErrorEmitter { +pub(crate) struct ErrorEmitter { wr: Box, } @@ -44,7 +44,7 @@ impl Emitter for ErrorEmitter { } } -pub fn try_with_handler(code: &str, op: F) -> Result> +pub(crate) fn try_with_handler(code: &str, op: F) -> Result> where F: FnOnce(&Handler) -> Result, { diff --git a/rust/parse_ast/src/lib.rs b/rust/parse_ast/src/lib.rs index 6ffe5074dc4..ff8790a93ae 100644 --- a/rust/parse_ast/src/lib.rs +++ b/rust/parse_ast/src/lib.rs @@ -17,7 +17,7 @@ mod ast_nodes; mod convert_ast; mod error_emit; -pub fn parse_ast(code: String, allow_return_outside_function: bool) -> Vec { +pub fn parse_ast(code: String, allow_return_outside_function: bool, jsx: bool) -> Vec { let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let target = EsVersion::EsNext; let syntax = Syntax::Es(EsSyntax { @@ -25,6 +25,7 @@ pub fn parse_ast(code: String, allow_return_outside_function: bool) -> Vec { import_attributes: true, explicit_resource_management: true, decorators: true, + jsx, ..Default::default() }); diff --git a/scripts/ast-types.js b/scripts/ast-types.js index fbfa8f80770..2483f27b8f4 100644 --- a/scripts/ast-types.js +++ b/scripts/ast-types.js @@ -23,6 +23,7 @@ * For encoded non-JavaScript AST nodes like TypeScript or JSX, we try to follow * the format of typescript-eslint, which can be derived from their playground * https://typescript-eslint.io/play/#showAST=es&fileType=.tsx + * For JSX, see also https://github.com/facebook/jsx/blob/main/AST.md */ /** @typedef {"Node"|"OptionalNode"|"NodeList"|"Annotations"|"InvalidAnnotations"|"String"|"FixedString"|"OptionalString"|"Float"} FieldType */ @@ -388,6 +389,98 @@ export const AST_NODES = { imported: 'local' } }, + JSXAttribute: { + estreeType: 'any', + fields: [ + ['name', 'Node'], + ['value', 'OptionalNode'] + ] + }, + JSXClosingElement: { + estreeType: 'any', + fields: [['name', 'Node']] + }, + JSXClosingFragment: { + estreeType: 'any', + fields: [] + }, + JSXElement: { + estreeType: 'any', + fields: [ + ['openingElement', 'Node'], + ['children', 'NodeList'], + ['closingElement', 'OptionalNode'] + ] + }, + JSXEmptyExpression: { + estreeType: 'any', + useMacro: false + }, + JSXExpressionContainer: { + estreeType: 'any', + fields: [['expression', 'Node']], + useMacro: false + }, + JSXFragment: { + estreeType: 'any', + fields: [ + ['openingFragment', 'Node'], + ['children', 'NodeList'], + ['closingFragment', 'Node'] + ] + }, + JSXIdentifier: { + estreeType: 'any', + fields: [['name', 'String']] + }, + JSXMemberExpression: { + estreeType: 'any', + fields: [ + ['object', 'Node'], + ['property', 'Node'] + ], + useMacro: false + }, + JSXNamespacedName: { + estreeType: 'any', + fields: [ + ['namespace', 'Node'], + ['name', 'Node'] + ], + useMacro: false + }, + JSXOpeningElement: { + estreeType: 'any', + fields: [ + ['name', 'Node'], + ['attributes', 'NodeList'] + ], + flags: ['selfClosing'], + useMacro: false + }, + JSXOpeningFragment: { + additionalFields: { + attributes: '[]', + selfClosing: 'false' + }, + estreeType: 'any' + }, + JSXSpreadAttribute: { + estreeType: 'any', + fields: [['argument', 'Node']], + useMacro: false + }, + JSXSpreadChild: { + estreeType: 'any', + fields: [['expression', 'Node']] + }, + JSXText: { + estreeType: 'any', + fields: [ + ['value', 'String'], + ['raw', 'String'] + ] + }, LabeledStatement: { fields: [ ['label', 'Node'], diff --git a/scripts/generate-buffer-parsers.js b/scripts/generate-buffer-parsers.js index 3292aa272d3..d6f9ce6e64a 100644 --- a/scripts/generate-buffer-parsers.js +++ b/scripts/generate-buffer-parsers.js @@ -1,6 +1,6 @@ import { writeFile } from 'node:fs/promises'; import { astNodeNamesWithFieldOrder } from './ast-types.js'; -import { firstLetterLowercase, generateNotEditFilesComment, lintTsFile } from './helpers.js'; +import { firstLettersLowercase, generateNotEditFilesComment, lintTsFile } from './helpers.js'; const notEditFilesComment = generateNotEditFilesComment(import.meta.url); @@ -60,7 +60,7 @@ const jsConverters = astNodeNamesWithFieldOrder.map(({ name, fields, node, origi parameters.push('position, buffer'); } } - return `function ${firstLetterLowercase(name)} (${parameters.join(', ')}) { + return `function ${firstLettersLowercase(name)} (${parameters.join(', ')}) { ${definitions.join('')}}`; }); diff --git a/scripts/generate-buffer-to-ast.js b/scripts/generate-buffer-to-ast.js index 6199e15abb2..4a0b971dae3 100644 --- a/scripts/generate-buffer-to-ast.js +++ b/scripts/generate-buffer-to-ast.js @@ -1,6 +1,6 @@ import { writeFile } from 'node:fs/promises'; import { astNodeNamesWithFieldOrder } from './ast-types.js'; -import { firstLetterLowercase, generateNotEditFilesComment, lintTsFile } from './helpers.js'; +import { firstLettersLowercase, generateNotEditFilesComment, lintTsFile } from './helpers.js'; const notEditFilesComment = generateNotEditFilesComment(import.meta.url); @@ -38,7 +38,7 @@ const jsConverters = astNodeNamesWithFieldOrder.map(({ name, fields, node, origi ...getFixedProperties(node), ...Object.entries(node.additionalFields || []).map(([key, value]) => `${key}: ${value}`) ]; - return `function ${firstLetterLowercase(name)} (position, buffer): ${name}Node { + return `function ${firstLettersLowercase(name)} (position, buffer): ${name}Node { ${definitions.join('')}return { type: '${node.astType || name}', start: buffer[position], diff --git a/scripts/helpers.js b/scripts/helpers.js index 9e1d9016096..246f975791e 100644 --- a/scripts/helpers.js +++ b/scripts/helpers.js @@ -99,11 +99,14 @@ export function lintRustFile(file) { } /** + * Replace the first letters with lowercase while maintaining camel casing * @param {string} string * @returns {string} */ -export function firstLetterLowercase(string) { - return string[0].toLowerCase() + string.slice(1); +export function firstLettersLowercase(string) { + return string.replace(/^[A-Z]+/, match => + match.length === 1 ? match.toLowerCase() : match.slice(0, -1).toLowerCase() + match.slice(-1) + ); } /** @@ -111,7 +114,7 @@ export function firstLetterLowercase(string) { * @returns {string} */ export function toSnakeCase(string) { - return string.replace(/(? void; addImport: (node: ImportDeclaration) => void; addImportMeta: (node: MetaProperty) => void; + addImportSource: (importSource: string) => void; code: string; deoptimizationTracker: PathTracker; error: (properties: RollupLog, pos: number) => never; fileName: string; getExports: () => string[]; + getImportedJsxFactoryVariable: (baseName: string, pos: number, importSource: string) => Variable; getModuleExecIndex: () => number; getModuleName: () => string; getNodeConstructor: (name: string) => typeof NodeBase; @@ -869,11 +872,13 @@ export default class Module { addExport: this.addExport.bind(this), addImport: this.addImport.bind(this), addImportMeta: this.addImportMeta.bind(this), + addImportSource: this.addImportSource.bind(this), code, // Only needed for debugging deoptimizationTracker: this.graph.deoptimizationTracker, error: this.error.bind(this), fileName, // Needed for warnings getExports: this.getExports.bind(this), + getImportedJsxFactoryVariable: this.getImportedJsxFactoryVariable.bind(this), getModuleExecIndex: () => this.execIndex, getModuleName: this.basename.bind(this), getNodeConstructor: (name: string) => nodeConstructors[name] || nodeConstructors.UnknownNode, @@ -906,7 +911,7 @@ export default class Module { } else { // Measuring asynchronous code does not provide reasonable results timeEnd('generate ast', 3); - const astBuffer = await parseAsync(code, false); + const astBuffer = await parseAsync(code, false, this.options.jsx !== false); timeStart('generate ast', 3); this.ast = convertProgram(astBuffer, programParent, this.scope); // Make lazy and apply LRU cache to not hog the memory @@ -1147,6 +1152,12 @@ export default class Module { } } + private addImportSource(importSource: string): void { + if (importSource && !this.sourcesWithAttributes.has(importSource)) { + this.sourcesWithAttributes.set(importSource, EMPTY_OBJECT); + } + } + private addImportMeta(node: MetaProperty): void { this.importMetas.push(node); } @@ -1233,6 +1244,20 @@ export default class Module { } } + private getImportedJsxFactoryVariable( + baseName: string, + nodeStart: number, + importSource: string + ): Variable { + const { id } = this.resolvedIds[importSource!]; + const module = this.graph.modulesById.get(id)!; + const [variable] = module.getVariableForExportName(baseName); + if (!variable) { + return this.error(logMissingJsxExport(baseName, id, this.id), nodeStart); + } + return variable; + } + private getVariableFromNamespaceReexports( name: string, importerForSideEffects?: Module, @@ -1386,7 +1411,7 @@ export default class Module { private tryParse() { try { - return parseAst(this.info.code!); + return parseAst(this.info.code!, { jsx: this.options.jsx !== false }); } catch (error_: any) { return this.error(logModuleParseError(error_, this.id), error_.pos); } diff --git a/src/ast/bufferParsers.ts b/src/ast/bufferParsers.ts index b197bd23ece..0c270c3ff60 100644 --- a/src/ast/bufferParsers.ts +++ b/src/ast/bufferParsers.ts @@ -48,6 +48,21 @@ import ImportDefaultSpecifier from './nodes/ImportDefaultSpecifier'; import ImportExpression from './nodes/ImportExpression'; import ImportNamespaceSpecifier from './nodes/ImportNamespaceSpecifier'; import ImportSpecifier from './nodes/ImportSpecifier'; +import JSXAttribute from './nodes/JSXAttribute'; +import JSXClosingElement from './nodes/JSXClosingElement'; +import JSXClosingFragment from './nodes/JSXClosingFragment'; +import JSXElement from './nodes/JSXElement'; +import JSXEmptyExpression from './nodes/JSXEmptyExpression'; +import JSXExpressionContainer from './nodes/JSXExpressionContainer'; +import JSXFragment from './nodes/JSXFragment'; +import JSXIdentifier from './nodes/JSXIdentifier'; +import JSXMemberExpression from './nodes/JSXMemberExpression'; +import JSXNamespacedName from './nodes/JSXNamespacedName'; +import JSXOpeningElement from './nodes/JSXOpeningElement'; +import JSXOpeningFragment from './nodes/JSXOpeningFragment'; +import JSXSpreadAttribute from './nodes/JSXSpreadAttribute'; +import JSXSpreadChild from './nodes/JSXSpreadChild'; +import JSXText from './nodes/JSXText'; import LabeledStatement from './nodes/LabeledStatement'; import Literal from './nodes/Literal'; import LogicalExpression from './nodes/LogicalExpression'; @@ -141,6 +156,21 @@ const nodeTypeStrings = [ 'ImportExpression', 'ImportNamespaceSpecifier', 'ImportSpecifier', + 'JSXAttribute', + 'JSXClosingElement', + 'JSXClosingFragment', + 'JSXElement', + 'JSXEmptyExpression', + 'JSXExpressionContainer', + 'JSXFragment', + 'JSXIdentifier', + 'JSXMemberExpression', + 'JSXNamespacedName', + 'JSXOpeningElement', + 'JSXOpeningFragment', + 'JSXSpreadAttribute', + 'JSXSpreadChild', + 'JSXText', 'LabeledStatement', 'Literal', 'Literal', @@ -224,6 +254,21 @@ const nodeConstructors: (typeof NodeBase)[] = [ ImportExpression, ImportNamespaceSpecifier, ImportSpecifier, + JSXAttribute, + JSXClosingElement, + JSXClosingFragment, + JSXElement, + JSXEmptyExpression, + JSXExpressionContainer, + JSXFragment, + JSXIdentifier, + JSXMemberExpression, + JSXNamespacedName, + JSXOpeningElement, + JSXOpeningFragment, + JSXSpreadAttribute, + JSXSpreadChild, + JSXText, LabeledStatement, Literal, Literal, @@ -563,6 +608,74 @@ const bufferParsers: ((node: any, position: number, buffer: AstBuffer) => void)[ node.imported = importedPosition === 0 ? node.local : convertNode(node, scope, importedPosition, buffer); }, + function jsxAttribute(node: JSXAttribute, position, buffer) { + const { scope } = node; + node.name = convertNode(node, scope, buffer[position], buffer); + const valuePosition = buffer[position + 1]; + node.value = valuePosition === 0 ? null : convertNode(node, scope, valuePosition, buffer); + }, + function jsxClosingElement(node: JSXClosingElement, position, buffer) { + const { scope } = node; + node.name = convertNode(node, scope, buffer[position], buffer); + }, + function jsxClosingFragment() {}, + function jsxElement(node: JSXElement, position, buffer) { + const { scope } = node; + node.openingElement = convertNode(node, scope, buffer[position], buffer); + node.children = convertNodeList(node, scope, buffer[position + 1], buffer); + const closingElementPosition = buffer[position + 2]; + node.closingElement = + closingElementPosition === 0 + ? null + : convertNode(node, scope, closingElementPosition, buffer); + }, + function jsxEmptyExpression() {}, + function jsxExpressionContainer(node: JSXExpressionContainer, position, buffer) { + const { scope } = node; + node.expression = convertNode(node, scope, buffer[position], buffer); + }, + function jsxFragment(node: JSXFragment, position, buffer) { + const { scope } = node; + node.openingFragment = convertNode(node, scope, buffer[position], buffer); + node.children = convertNodeList(node, scope, buffer[position + 1], buffer); + node.closingFragment = convertNode(node, scope, buffer[position + 2], buffer); + }, + function jsxIdentifier(node: JSXIdentifier, position, buffer) { + node.name = buffer.convertString(buffer[position]); + }, + function jsxMemberExpression(node: JSXMemberExpression, position, buffer) { + const { scope } = node; + node.object = convertNode(node, scope, buffer[position], buffer); + node.property = convertNode(node, scope, buffer[position + 1], buffer); + }, + function jsxNamespacedName(node: JSXNamespacedName, position, buffer) { + const { scope } = node; + node.namespace = convertNode(node, scope, buffer[position], buffer); + node.name = convertNode(node, scope, buffer[position + 1], buffer); + }, + function jsxOpeningElement(node: JSXOpeningElement, position, buffer) { + const { scope } = node; + const flags = buffer[position]; + node.selfClosing = (flags & 1) === 1; + node.name = convertNode(node, scope, buffer[position + 1], buffer); + node.attributes = convertNodeList(node, scope, buffer[position + 2], buffer); + }, + function jsxOpeningFragment(node: JSXOpeningFragment) { + node.attributes = []; + node.selfClosing = false; + }, + function jsxSpreadAttribute(node: JSXSpreadAttribute, position, buffer) { + const { scope } = node; + node.argument = convertNode(node, scope, buffer[position], buffer); + }, + function jsxSpreadChild(node: JSXSpreadChild, position, buffer) { + const { scope } = node; + node.expression = convertNode(node, scope, buffer[position], buffer); + }, + function jsxText(node: JSXText, position, buffer) { + node.value = buffer.convertString(buffer[position]); + node.raw = buffer.convertString(buffer[position + 1]); + }, function labeledStatement(node: LabeledStatement, position, buffer) { const { scope } = node; node.label = convertNode(node, scope, buffer[position], buffer); diff --git a/src/ast/childNodeKeys.ts b/src/ast/childNodeKeys.ts index e6ab43b65af..2658d723d42 100644 --- a/src/ast/childNodeKeys.ts +++ b/src/ast/childNodeKeys.ts @@ -41,6 +41,21 @@ export const childNodeKeys: Record = { ImportExpression: ['source', 'options'], ImportNamespaceSpecifier: ['local'], ImportSpecifier: ['imported', 'local'], + JSXAttribute: ['name', 'value'], + JSXClosingElement: ['name'], + JSXClosingFragment: [], + JSXElement: ['openingElement', 'children', 'closingElement'], + JSXEmptyExpression: [], + JSXExpressionContainer: ['expression'], + JSXFragment: ['openingFragment', 'children', 'closingFragment'], + JSXIdentifier: [], + JSXMemberExpression: ['object', 'property'], + JSXNamespacedName: ['namespace', 'name'], + JSXOpeningElement: ['name', 'attributes'], + JSXOpeningFragment: [], + JSXSpreadAttribute: ['argument'], + JSXSpreadChild: ['expression'], + JSXText: [], LabeledStatement: ['label', 'body'], Literal: [], LogicalExpression: ['left', 'right'], diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 771526ef80a..c513e800b9f 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -1,57 +1,23 @@ import isReference, { type NodeWithFieldDefinition } from 'is-reference'; import type MagicString from 'magic-string'; -import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import { BLANK } from '../../utils/blank'; -import { logIllegalImportReassignment } from '../../utils/logs'; -import { PureFunctionKey } from '../../utils/pureFunctions'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { markModuleAndImpureDependenciesAsExecuted } from '../../utils/traverseStaticDependencies'; -import type { DeoptimizableEntity } from '../DeoptimizableEntity'; -import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeInteraction, NodeInteractionCalled } from '../NodeInteractions'; -import { - INTERACTION_ACCESSED, - INTERACTION_ASSIGNED, - INTERACTION_CALLED, - NODE_INTERACTION_UNKNOWN_ACCESS -} from '../NodeInteractions'; import type FunctionScope from '../scopes/FunctionScope'; -import { EMPTY_PATH, type ObjectPath, type PathTracker } from '../utils/PathTracker'; -import GlobalVariable from '../variables/GlobalVariable'; -import LocalVariable from '../variables/LocalVariable'; +import type LocalVariable from '../variables/LocalVariable'; import type Variable from '../variables/Variable'; import * as NodeType from './NodeType'; -import { Flag, isFlagSet, setFlag } from './shared/BitFlags'; -import { - type ExpressionEntity, - type LiteralValueOrUnknown, - UNKNOWN_EXPRESSION -} from './shared/Expression'; -import { NodeBase } from './shared/Node'; +import { type ExpressionEntity } from './shared/Expression'; +import IdentifierBase from './shared/IdentifierBase'; import type { PatternNode } from './shared/Pattern'; import type { VariableKind } from './shared/VariableKinds'; -import type SpreadElement from './SpreadElement'; export type IdentifierWithVariable = Identifier & { variable: Variable }; -const tdzVariableKinds = new Set(['class', 'const', 'let', 'var', 'using', 'await using']); - -export default class Identifier extends NodeBase implements PatternNode { - declare name: string; - declare type: NodeType.tIdentifier; +export default class Identifier extends IdentifierBase implements PatternNode { + name!: string; + type!: NodeType.tIdentifier; variable: Variable | null = null; - private get isTDZAccess(): boolean | null { - if (!isFlagSet(this.flags, Flag.tdzAccessDefined)) { - return null; - } - return isFlagSet(this.flags, Flag.tdzAccess); - } - private set isTDZAccess(value: boolean) { - this.flags = setFlag(this.flags, Flag.tdzAccessDefined, true); - this.flags = setFlag(this.flags, Flag.tdzAccess, value); - } - addExportedVariables( variables: Variable[], exportNamesByVariable: ReadonlyMap @@ -61,12 +27,11 @@ export default class Identifier extends NodeBase implements PatternNode { } } - private isReferenceVariable = false; bind(): void { if (!this.variable && isReference(this, this.parent as NodeWithFieldDefinition)) { this.variable = this.scope.findVariable(this.name); this.variable.addReference(this); - this.isReferenceVariable = true; + this.isVariableReference = true; } } @@ -108,150 +73,6 @@ export default class Identifier extends NodeBase implements PatternNode { return [(this.variable = variable)]; } - deoptimizeArgumentsOnInteractionAtPath( - interaction: NodeInteraction, - path: ObjectPath, - recursionTracker: PathTracker - ): void { - this.variable!.deoptimizeArgumentsOnInteractionAtPath(interaction, path, recursionTracker); - } - - deoptimizePath(path: ObjectPath): void { - if (path.length === 0 && !this.scope.contains(this.name)) { - this.disallowImportReassignment(); - } - // We keep conditional chaining because an unknown Node could have an - // Identifier as property that might be deoptimized by default - this.variable?.deoptimizePath(path); - } - - getLiteralValueAtPath( - path: ObjectPath, - recursionTracker: PathTracker, - origin: DeoptimizableEntity - ): LiteralValueOrUnknown { - return this.getVariableRespectingTDZ()!.getLiteralValueAtPath(path, recursionTracker, origin); - } - - getReturnExpressionWhenCalledAtPath( - path: ObjectPath, - interaction: NodeInteractionCalled, - recursionTracker: PathTracker, - origin: DeoptimizableEntity - ): [expression: ExpressionEntity, isPure: boolean] { - const [expression, isPure] = - this.getVariableRespectingTDZ()!.getReturnExpressionWhenCalledAtPath( - path, - interaction, - recursionTracker, - origin - ); - return [expression, isPure || this.isPureFunction(path)]; - } - - hasEffects(context: HasEffectsContext): boolean { - if (!this.deoptimized) this.applyDeoptimizations(); - if (this.isPossibleTDZ() && this.variable!.kind !== 'var') { - return true; - } - return ( - (this.scope.context.options.treeshake as NormalizedTreeshakingOptions) - .unknownGlobalSideEffects && - this.variable instanceof GlobalVariable && - !this.isPureFunction(EMPTY_PATH) && - this.variable.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - NODE_INTERACTION_UNKNOWN_ACCESS, - context - ) - ); - } - - hasEffectsOnInteractionAtPath( - path: ObjectPath, - interaction: NodeInteraction, - context: HasEffectsContext - ): boolean { - switch (interaction.type) { - case INTERACTION_ACCESSED: { - return ( - this.variable !== null && - !this.isPureFunction(path) && - this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(path, interaction, context) - ); - } - case INTERACTION_ASSIGNED: { - return ( - path.length > 0 ? this.getVariableRespectingTDZ() : this.variable - )!.hasEffectsOnInteractionAtPath(path, interaction, context); - } - case INTERACTION_CALLED: { - return ( - !this.isPureFunction(path) && - this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(path, interaction, context) - ); - } - } - } - - include(): void { - if (!this.deoptimized) this.applyDeoptimizations(); - if (!this.included) { - this.included = true; - if (this.variable !== null) { - this.scope.context.includeVariableInModule(this.variable); - } - } - } - - includeCallArguments( - context: InclusionContext, - parameters: readonly (ExpressionEntity | SpreadElement)[] - ): void { - this.variable!.includeCallArguments(context, parameters); - } - - isPossibleTDZ(): boolean { - // return cached value to avoid issues with the next tree-shaking pass - const cachedTdzAccess = this.isTDZAccess; - if (cachedTdzAccess !== null) return cachedTdzAccess; - - if ( - !( - this.variable instanceof LocalVariable && - this.variable.kind && - tdzVariableKinds.has(this.variable.kind) && - // We ignore modules that did not receive a treeshaking pass yet as that - // causes many false positives due to circular dependencies or disabled - // moduleSideEffects. - this.variable.module.hasTreeShakingPassStarted - ) - ) { - return (this.isTDZAccess = false); - } - - let decl_id; - if ( - this.variable.declarations && - this.variable.declarations.length === 1 && - (decl_id = this.variable.declarations[0] as any) && - this.start < decl_id.start && - closestParentFunctionOrProgram(this) === closestParentFunctionOrProgram(decl_id) - ) { - // a variable accessed before its declaration - // in the same function or at top level of module - return (this.isTDZAccess = true); - } - - if (!this.variable.initReached) { - // Either a const/let TDZ violation or - // var use before declaration was encountered. - return (this.isTDZAccess = true); - } - - return (this.isTDZAccess = false); - } - markDeclarationReached(): void { this.variable!.initReached = true; } @@ -283,59 +104,4 @@ export default class Identifier extends NodeBase implements PatternNode { } } } - - private disallowImportReassignment(): never { - return this.scope.context.error( - logIllegalImportReassignment(this.name, this.scope.context.module.id), - this.start - ); - } - - protected applyDeoptimizations(): void { - this.deoptimized = true; - if (this.variable instanceof LocalVariable) { - // When accessing a variable from a module without side effects, this - // means we use an export of that module and therefore need to potentially - // include it in the bundle. - if (!this.variable.module.isExecuted) { - markModuleAndImpureDependenciesAsExecuted(this.variable.module); - } - this.variable.consolidateInitializers(); - this.scope.context.requestTreeshakingPass(); - } - if (this.isReferenceVariable) { - this.variable!.addUsedPlace(this); - this.scope.context.requestTreeshakingPass(); - } - } - - private getVariableRespectingTDZ(): ExpressionEntity | null { - if (this.isPossibleTDZ()) { - return UNKNOWN_EXPRESSION; - } - return this.variable; - } - - private isPureFunction(path: ObjectPath) { - let currentPureFunction = this.scope.context.manualPureFunctions[this.name]; - for (const segment of path) { - if (currentPureFunction) { - if (currentPureFunction[PureFunctionKey]) { - return true; - } - currentPureFunction = currentPureFunction[segment as string]; - } else { - return false; - } - } - return currentPureFunction?.[PureFunctionKey] as boolean; - } -} - -function closestParentFunctionOrProgram(node: any): any { - while (node && !/^Program|Function/.test(node.type)) { - node = node.parent; - } - // one of: ArrowFunctionExpression, FunctionDeclaration, FunctionExpression or Program - return node; } diff --git a/src/ast/nodes/JSXAttribute.ts b/src/ast/nodes/JSXAttribute.ts new file mode 100644 index 00000000000..b16287d9de2 --- /dev/null +++ b/src/ast/nodes/JSXAttribute.ts @@ -0,0 +1,38 @@ +import type MagicString from 'magic-string'; +import { BLANK } from '../../utils/blank'; +import { stringifyObjectKeyIfNeeded } from '../../utils/identifierHelpers'; +import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; +import type JSXElement from './JSXElement'; +import type JSXExpressionContainer from './JSXExpressionContainer'; +import type JSXFragment from './JSXFragment'; +import JSXIdentifier from './JSXIdentifier'; +import type JSXNamespacedName from './JSXNamespacedName'; +import type Literal from './Literal'; +import type * as NodeType from './NodeType'; +import { NodeBase } from './shared/Node'; + +export default class JSXAttribute extends NodeBase { + type!: NodeType.tJSXAttribute; + name!: JSXIdentifier | JSXNamespacedName; + value!: Literal | JSXExpressionContainer | JSXElement | JSXFragment | null; + + render(code: MagicString, options: RenderOptions, { jsxMode }: NodeRenderOptions = BLANK): void { + super.render(code, options); + if ((['classic', 'automatic'] as (string | undefined)[]).includes(jsxMode)) { + const { name, value } = this; + const key = + name instanceof JSXIdentifier ? name.name : `${name.namespace.name}:${name.name.name}`; + if (!(jsxMode === 'automatic' && key === 'key')) { + const safeKey = stringifyObjectKeyIfNeeded(key); + if (key !== safeKey) { + code.overwrite(name.start, name.end, safeKey, { contentOnly: true }); + } + if (value) { + code.overwrite(name.end, value.start, ': ', { contentOnly: true }); + } else { + code.appendLeft(name.end, ': true'); + } + } + } + } +} diff --git a/src/ast/nodes/JSXClosingElement.ts b/src/ast/nodes/JSXClosingElement.ts new file mode 100644 index 00000000000..8fdd4500943 --- /dev/null +++ b/src/ast/nodes/JSXClosingElement.ts @@ -0,0 +1,10 @@ +import type JSXIdentifier from './JSXIdentifier'; +import type JSXMemberExpression from './JSXMemberExpression'; +import type JSXNamespacedName from './JSXNamespacedName'; +import type * as NodeType from './NodeType'; +import JSXClosingBase from './shared/JSXClosingBase'; + +export default class JSXClosingElement extends JSXClosingBase { + type!: NodeType.tJSXClosingElement; + name!: JSXIdentifier | JSXMemberExpression | JSXNamespacedName; +} diff --git a/src/ast/nodes/JSXClosingFragment.ts b/src/ast/nodes/JSXClosingFragment.ts new file mode 100644 index 00000000000..a562f09de0b --- /dev/null +++ b/src/ast/nodes/JSXClosingFragment.ts @@ -0,0 +1,6 @@ +import type * as NodeType from './NodeType'; +import JSXClosingBase from './shared/JSXClosingBase'; + +export default class JSXClosingFragment extends JSXClosingBase { + type!: NodeType.tJSXClosingFragment; +} diff --git a/src/ast/nodes/JSXElement.ts b/src/ast/nodes/JSXElement.ts new file mode 100644 index 00000000000..cd5ab4f449a --- /dev/null +++ b/src/ast/nodes/JSXElement.ts @@ -0,0 +1,249 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../rollup/types'; +import type { RenderOptions } from '../../utils/renderHelpers'; +import JSXAttribute from './JSXAttribute'; +import type JSXClosingElement from './JSXClosingElement'; +import type JSXOpeningElement from './JSXOpeningElement'; +import JSXSpreadAttribute from './JSXSpreadAttribute'; +import type * as NodeType from './NodeType'; +import JSXElementBase from './shared/JSXElementBase'; +import type { JSXChild, JsxMode } from './shared/jsxHelpers'; + +export default class JSXElement extends JSXElementBase { + type!: NodeType.tJSXElement; + openingElement!: JSXOpeningElement; + closingElement!: JSXClosingElement | null; + children!: JSXChild[]; + + render(code: MagicString, options: RenderOptions): void { + switch (this.jsxMode.mode) { + case 'classic': { + this.renderClassicMode(code, options); + break; + } + case 'automatic': { + this.renderAutomaticMode(code, options); + break; + } + default: { + super.render(code, options); + } + } + } + + protected getRenderingMode(): JsxMode { + const jsx = this.scope.context.options.jsx as NormalizedJsxOptions; + const { mode, factory, importSource } = jsx; + if (mode === 'automatic') { + // In the case there is a key after a spread attribute, we fall back to + // classic mode, see https://github.com/facebook/react/issues/20031#issuecomment-710346866 + // for reasoning. + let hasSpread = false; + for (const attribute of this.openingElement.attributes) { + if (attribute instanceof JSXSpreadAttribute) { + hasSpread = true; + } else if (hasSpread && attribute.name.name === 'key') { + return { factory, importSource, mode: 'classic' }; + } + } + } + return super.getRenderingMode(); + } + + private renderClassicMode(code: MagicString, options: RenderOptions) { + const { + snippets: { getPropertyAccess }, + useOriginalName + } = options; + const { + closingElement, + end, + factory, + factoryVariable, + openingElement: { end: openingEnd, selfClosing } + } = this; + const [, ...nestedName] = factory!.split('.'); + const { firstAttribute, hasAttributes, hasSpread, inObject, previousEnd } = + this.renderAttributes( + code, + options, + [factoryVariable!.getName(getPropertyAccess, useOriginalName), ...nestedName].join('.'), + false + ); + + this.wrapAttributes( + code, + inObject, + hasAttributes, + hasSpread, + firstAttribute, + 'null', + previousEnd + ); + + this.renderChildren(code, options, openingEnd); + + if (selfClosing) { + code.appendLeft(end, ')'); + } else { + closingElement!.render(code, options); + } + } + + private renderAutomaticMode(code: MagicString, options: RenderOptions) { + const { + snippets: { getPropertyAccess }, + useOriginalName + } = options; + const { + closingElement, + end, + factoryVariable, + openingElement: { end: openindEnd, selfClosing } + } = this; + let { firstAttribute, hasAttributes, hasSpread, inObject, keyAttribute, previousEnd } = + this.renderAttributes( + code, + options, + factoryVariable!.getName(getPropertyAccess, useOriginalName), + true + ); + + const { firstChild, hasMultipleChildren, childrenEnd } = this.renderChildren( + code, + options, + openindEnd + ); + + if (firstChild) { + code.prependRight(firstChild.start, `children: ${hasMultipleChildren ? '[' : ''}`); + if (!inObject) { + code.prependRight(firstChild.start, '{ '); + inObject = true; + } + previousEnd = closingElement!.start; + if (hasMultipleChildren) { + code.appendLeft(previousEnd, ']'); + } + } + + this.wrapAttributes( + code, + inObject, + hasAttributes || !!firstChild, + hasSpread, + firstAttribute || firstChild, + '{}', + childrenEnd + ); + + if (keyAttribute) { + const { value } = keyAttribute; + // This will appear to the left of the moved code... + code.appendLeft(childrenEnd, ', '); + if (value) { + code.move(value.start, value.end, childrenEnd); + } else { + code.appendLeft(childrenEnd, 'true'); + } + } + + if (selfClosing) { + // Moving the key attribute will also move the parenthesis to the right position + code.appendLeft(keyAttribute?.value?.end || end, ')'); + } else { + closingElement!.render(code, options); + } + } + + private renderAttributes( + code: MagicString, + options: RenderOptions, + factoryName: string, + extractKeyAttribute: boolean + ): { + firstAttribute: JSXAttribute | JSXSpreadAttribute | JSXChild | null; + hasAttributes: boolean; + hasSpread: boolean; + inObject: boolean; + keyAttribute: JSXAttribute | null; + previousEnd: number; + } { + const { + jsxMode: { mode }, + openingElement + } = this; + const { + attributes, + end: openingEnd, + start: openingStart, + name: { start: nameStart, end: nameEnd } + } = openingElement; + code.update(openingStart, nameStart, `/*#__PURE__*/${factoryName}(`); + openingElement.render(code, options, { jsxMode: mode }); + let keyAttribute: JSXAttribute | null = null; + let hasSpread = false; + let inObject = false; + let previousEnd = nameEnd; + let hasAttributes = false; + let firstAttribute: JSXAttribute | JSXSpreadAttribute | null = null; + for (const attribute of attributes) { + if (attribute instanceof JSXAttribute) { + if (extractKeyAttribute && attribute.name.name === 'key') { + keyAttribute = attribute; + code.remove(previousEnd, attribute.value?.start || attribute.end); + continue; + } + code.appendLeft(previousEnd, ','); + if (!inObject) { + code.prependRight(attribute.start, '{ '); + inObject = true; + } + hasAttributes = true; + } else { + if (inObject) { + if (hasAttributes) { + code.appendLeft(previousEnd, ' '); + } + code.appendLeft(previousEnd, '},'); + inObject = false; + } else { + code.appendLeft(previousEnd, ','); + } + hasSpread = true; + } + previousEnd = attribute.end; + if (!firstAttribute) { + firstAttribute = attribute; + } + } + code.remove(attributes.at(-1)?.end || previousEnd, openingEnd); + return { firstAttribute, hasAttributes, hasSpread, inObject, keyAttribute, previousEnd }; + } + + private wrapAttributes( + code: MagicString, + inObject: boolean, + hasAttributes: boolean, + hasSpread: boolean, + firstAttribute: JSXAttribute | JSXSpreadAttribute | JSXChild | null, + missingAttributesFallback: string, + attributesEnd: number + ) { + if (inObject) { + code.appendLeft(attributesEnd, ' }'); + } + if (hasSpread) { + if (hasAttributes) { + const { start } = firstAttribute!; + if (firstAttribute instanceof JSXSpreadAttribute) { + code.prependRight(start, '{}, '); + } + code.prependRight(start, 'Object.assign('); + code.appendLeft(attributesEnd, ')'); + } + } else if (!hasAttributes) { + code.appendLeft(attributesEnd, `, ${missingAttributesFallback}`); + } + } +} diff --git a/src/ast/nodes/JSXEmptyExpression.ts b/src/ast/nodes/JSXEmptyExpression.ts new file mode 100644 index 00000000000..87d3b29a1b3 --- /dev/null +++ b/src/ast/nodes/JSXEmptyExpression.ts @@ -0,0 +1,6 @@ +import type * as NodeType from './NodeType'; +import { NodeBase } from './shared/Node'; + +export default class JSXEmptyExpression extends NodeBase { + type!: NodeType.tJSXEmptyExpression; +} diff --git a/src/ast/nodes/JSXExpressionContainer.ts b/src/ast/nodes/JSXExpressionContainer.ts new file mode 100644 index 00000000000..d4b001629c2 --- /dev/null +++ b/src/ast/nodes/JSXExpressionContainer.ts @@ -0,0 +1,21 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../rollup/types'; +import type { RenderOptions } from '../../utils/renderHelpers'; +import type JSXEmptyExpression from './JSXEmptyExpression'; +import type * as NodeType from './NodeType'; +import type { ExpressionNode } from './shared/Node'; +import { NodeBase } from './shared/Node'; + +export default class JSXExpressionContainer extends NodeBase { + type!: NodeType.tJSXExpressionContainer; + expression!: ExpressionNode | JSXEmptyExpression; + + render(code: MagicString, options: RenderOptions): void { + const { mode } = this.scope.context.options.jsx as NormalizedJsxOptions; + if (mode !== 'preserve') { + code.remove(this.start, this.expression.start); + code.remove(this.expression.end, this.end); + } + this.expression.render(code, options); + } +} diff --git a/src/ast/nodes/JSXFragment.ts b/src/ast/nodes/JSXFragment.ts new file mode 100644 index 00000000000..a239459c866 --- /dev/null +++ b/src/ast/nodes/JSXFragment.ts @@ -0,0 +1,83 @@ +import type MagicString from 'magic-string'; +import type { RenderOptions } from '../../utils/renderHelpers'; +import type JSXClosingFragment from './JSXClosingFragment'; +import type JSXOpeningFragment from './JSXOpeningFragment'; +import type * as NodeType from './NodeType'; +import JSXElementBase from './shared/JSXElementBase'; +import type { JSXChild } from './shared/jsxHelpers'; + +export default class JSXFragment extends JSXElementBase { + type!: NodeType.tJSXElement; + openingFragment!: JSXOpeningFragment; + children!: JSXChild[]; + closingFragment!: JSXClosingFragment; + + render(code: MagicString, options: RenderOptions): void { + switch (this.jsxMode.mode) { + case 'classic': { + this.renderClassicMode(code, options); + break; + } + case 'automatic': { + this.renderAutomaticMode(code, options); + break; + } + default: { + super.render(code, options); + } + } + } + + private renderClassicMode(code: MagicString, options: RenderOptions) { + const { + snippets: { getPropertyAccess }, + useOriginalName + } = options; + const { closingFragment, factory, factoryVariable, openingFragment, start } = this; + const [, ...nestedName] = factory!.split('.'); + openingFragment.render(code, options); + code.prependRight( + start, + `/*#__PURE__*/${[ + factoryVariable!.getName(getPropertyAccess, useOriginalName), + ...nestedName + ].join('.')}(` + ); + code.appendLeft(openingFragment.end, ', null'); + + this.renderChildren(code, options, openingFragment.end); + + closingFragment.render(code, options); + } + + private renderAutomaticMode(code: MagicString, options: RenderOptions) { + const { + snippets: { getPropertyAccess }, + useOriginalName + } = options; + const { closingFragment, factoryVariable, openingFragment, start } = this; + openingFragment.render(code, options); + code.prependRight( + start, + `/*#__PURE__*/${factoryVariable!.getName(getPropertyAccess, useOriginalName)}(` + ); + + const { firstChild, hasMultipleChildren, childrenEnd } = this.renderChildren( + code, + options, + openingFragment.end + ); + + if (firstChild) { + code.prependRight(firstChild.start, `{ children: ${hasMultipleChildren ? '[' : ''}`); + if (hasMultipleChildren) { + code.appendLeft(closingFragment.start, ']'); + } + code.appendLeft(childrenEnd, ' }'); + } else { + code.appendLeft(openingFragment.end, ', {}'); + } + + closingFragment.render(code, options); + } +} diff --git a/src/ast/nodes/JSXIdentifier.ts b/src/ast/nodes/JSXIdentifier.ts new file mode 100644 index 00000000000..f7576328311 --- /dev/null +++ b/src/ast/nodes/JSXIdentifier.ts @@ -0,0 +1,74 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../rollup/types'; +import type { RenderOptions } from '../../utils/renderHelpers'; +import type JSXMemberExpression from './JSXMemberExpression'; +import type * as NodeType from './NodeType'; +import IdentifierBase from './shared/IdentifierBase'; + +const enum IdentifierType { + Reference, + NativeElementName, + Other +} + +export default class JSXIdentifier extends IdentifierBase { + type!: NodeType.tJSXIdentifier; + name!: string; + + private isNativeElement = false; + + bind(): void { + const type = this.getType(); + if (type === IdentifierType.Reference) { + this.variable = this.scope.findVariable(this.name); + this.variable.addReference(this); + } else if (type === IdentifierType.NativeElementName) { + this.isNativeElement = true; + } + } + + render( + code: MagicString, + { snippets: { getPropertyAccess }, useOriginalName }: RenderOptions + ): void { + if (this.variable) { + const name = this.variable.getName(getPropertyAccess, useOriginalName); + + if (name !== this.name) { + code.overwrite(this.start, this.end, name, { + contentOnly: true, + storeName: true + }); + } + } else if ( + this.isNativeElement && + (this.scope.context.options.jsx as NormalizedJsxOptions).mode !== 'preserve' + ) { + code.update(this.start, this.end, JSON.stringify(this.name)); + } + } + + private getType(): IdentifierType { + switch (this.parent.type) { + case 'JSXOpeningElement': + case 'JSXClosingElement': { + return this.name.startsWith(this.name.charAt(0).toUpperCase()) + ? IdentifierType.Reference + : IdentifierType.NativeElementName; + } + case 'JSXMemberExpression': { + return (this.parent as JSXMemberExpression).object === this + ? IdentifierType.Reference + : IdentifierType.Other; + } + case 'JSXAttribute': + case 'JSXNamespacedName': { + return IdentifierType.Other; + } + default: { + /* istanbul ignore next */ + throw new Error(`Unexpected parent node type for JSXIdentifier: ${this.parent.type}`); + } + } + } +} diff --git a/src/ast/nodes/JSXMemberExpression.ts b/src/ast/nodes/JSXMemberExpression.ts new file mode 100644 index 00000000000..1212066f3c7 --- /dev/null +++ b/src/ast/nodes/JSXMemberExpression.ts @@ -0,0 +1,9 @@ +import type JSXIdentifier from './JSXIdentifier'; +import type * as NodeType from './NodeType'; +import { NodeBase } from './shared/Node'; + +export default class JSXMemberExpression extends NodeBase { + type!: NodeType.tJSXMemberExpression; + object!: JSXMemberExpression | JSXIdentifier; + property!: JSXIdentifier; +} diff --git a/src/ast/nodes/JSXNamespacedName.ts b/src/ast/nodes/JSXNamespacedName.ts new file mode 100644 index 00000000000..5172b2a47a0 --- /dev/null +++ b/src/ast/nodes/JSXNamespacedName.ts @@ -0,0 +1,9 @@ +import type JSXIdentifier from './JSXIdentifier'; +import type * as NodeType from './NodeType'; +import { NodeBase } from './shared/Node'; + +export default class JSXNamespacedName extends NodeBase { + type!: NodeType.tJSXNamespacedName; + name!: JSXIdentifier; + namespace!: JSXIdentifier; +} diff --git a/src/ast/nodes/JSXOpeningElement.ts b/src/ast/nodes/JSXOpeningElement.ts new file mode 100644 index 00000000000..a7a57f06a72 --- /dev/null +++ b/src/ast/nodes/JSXOpeningElement.ts @@ -0,0 +1,30 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../rollup/types'; +import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; +import type JSXAttribute from './JSXAttribute'; +import type JSXIdentifier from './JSXIdentifier'; +import type JSXMemberExpression from './JSXMemberExpression'; +import type JSXNamespacedName from './JSXNamespacedName'; +import type JSXSpreadAttribute from './JSXSpreadAttribute'; +import type * as NodeType from './NodeType'; +import { NodeBase } from './shared/Node'; + +export default class JSXOpeningElement extends NodeBase { + type!: NodeType.tJSXOpeningElement; + name!: JSXIdentifier | JSXMemberExpression | JSXNamespacedName; + attributes!: (JSXAttribute | JSXSpreadAttribute)[]; + selfClosing!: boolean; + + render( + code: MagicString, + options: RenderOptions, + { + jsxMode = (this.scope.context.options.jsx as NormalizedJsxOptions).mode + }: NodeRenderOptions = {} + ): void { + this.name.render(code, options); + for (const attribute of this.attributes) { + attribute.render(code, options, { jsxMode }); + } + } +} diff --git a/src/ast/nodes/JSXOpeningFragment.ts b/src/ast/nodes/JSXOpeningFragment.ts new file mode 100644 index 00000000000..6d50740c150 --- /dev/null +++ b/src/ast/nodes/JSXOpeningFragment.ts @@ -0,0 +1,60 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../rollup/types'; +import type { RenderOptions } from '../../utils/renderHelpers'; +import type { InclusionContext } from '../ExecutionContext'; +import type Variable from '../variables/Variable'; +import type * as NodeType from './NodeType'; +import { getAndIncludeFactoryVariable } from './shared/jsxHelpers'; +import { type IncludeChildren, NodeBase } from './shared/Node'; + +export default class JSXOpeningFragment extends NodeBase { + type!: NodeType.tJSXOpeningElement; + attributes!: never[]; + selfClosing!: false; + + private fragment: string | null = null; + private fragmentVariable: Variable | null = null; + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + if (!this.included) { + const jsx = this.scope.context.options.jsx as NormalizedJsxOptions; + if (jsx.mode === 'automatic') { + this.fragment = 'Fragment'; + this.fragmentVariable = getAndIncludeFactoryVariable( + 'Fragment', + false, + jsx.jsxImportSource, + this + ); + } else { + const { fragment, importSource, mode } = jsx; + if (fragment != null) { + this.fragment = fragment; + this.fragmentVariable = getAndIncludeFactoryVariable( + fragment, + mode === 'preserve', + importSource, + this + ); + } + } + } + super.include(context, includeChildrenRecursively); + } + + render(code: MagicString, options: RenderOptions): void { + const { mode } = this.scope.context.options.jsx as NormalizedJsxOptions; + if (mode !== 'preserve') { + const { + snippets: { getPropertyAccess }, + useOriginalName + } = options; + const [, ...nestedFragment] = this.fragment!.split('.'); + const fragment = [ + this.fragmentVariable!.getName(getPropertyAccess, useOriginalName), + ...nestedFragment + ].join('.'); + code.update(this.start, this.end, fragment); + } + } +} diff --git a/src/ast/nodes/JSXSpreadAttribute.ts b/src/ast/nodes/JSXSpreadAttribute.ts new file mode 100644 index 00000000000..96f846a6c6c --- /dev/null +++ b/src/ast/nodes/JSXSpreadAttribute.ts @@ -0,0 +1,20 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../rollup/types'; +import type { RenderOptions } from '../../utils/renderHelpers'; +import type * as NodeType from './NodeType'; +import type { ExpressionNode } from './shared/Node'; +import { NodeBase } from './shared/Node'; + +export default class JSXSpreadAttribute extends NodeBase { + type!: NodeType.tJSXSpreadAttribute; + argument!: ExpressionNode; + + render(code: MagicString, options: RenderOptions): void { + this.argument.render(code, options); + const { mode } = this.scope.context.options.jsx as NormalizedJsxOptions; + if (mode !== 'preserve') { + code.overwrite(this.start, this.argument.start, '', { contentOnly: true }); + code.overwrite(this.argument.end, this.end, '', { contentOnly: true }); + } + } +} diff --git a/src/ast/nodes/JSXSpreadChild.ts b/src/ast/nodes/JSXSpreadChild.ts new file mode 100644 index 00000000000..f831e582692 --- /dev/null +++ b/src/ast/nodes/JSXSpreadChild.ts @@ -0,0 +1,20 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../rollup/types'; +import type { RenderOptions } from '../../utils/renderHelpers'; +import type * as NodeType from './NodeType'; +import type { ExpressionNode } from './shared/Node'; +import { NodeBase } from './shared/Node'; + +export default class JSXSpreadChild extends NodeBase { + type!: NodeType.tJSXSpreadChild; + expression!: ExpressionNode; + + render(code: MagicString, options: RenderOptions): void { + super.render(code, options); + const { mode } = this.scope.context.options.jsx as NormalizedJsxOptions; + if (mode !== 'preserve') { + code.overwrite(this.start, this.expression.start, '...', { contentOnly: true }); + code.overwrite(this.expression.end, this.end, '', { contentOnly: true }); + } + } +} diff --git a/src/ast/nodes/JSXText.ts b/src/ast/nodes/JSXText.ts new file mode 100644 index 00000000000..59e905cda2f --- /dev/null +++ b/src/ast/nodes/JSXText.ts @@ -0,0 +1,19 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../rollup/types'; +import type * as NodeType from './NodeType'; +import { NodeBase } from './shared/Node'; + +export default class JSXText extends NodeBase { + type!: NodeType.tJSXText; + value!: string; + raw!: string; + + render(code: MagicString) { + const { mode } = this.scope.context.options.jsx as NormalizedJsxOptions; + if (mode !== 'preserve') { + code.overwrite(this.start, this.end, JSON.stringify(this.value), { + contentOnly: true + }); + } + } +} diff --git a/src/ast/nodes/NodeType.ts b/src/ast/nodes/NodeType.ts index f3dfbb7d794..65ef0e1a16d 100644 --- a/src/ast/nodes/NodeType.ts +++ b/src/ast/nodes/NodeType.ts @@ -40,6 +40,21 @@ export type tImportDefaultSpecifier = 'ImportDefaultSpecifier'; export type tImportExpression = 'ImportExpression'; export type tImportNamespaceSpecifier = 'ImportNamespaceSpecifier'; export type tImportSpecifier = 'ImportSpecifier'; +export type tJSXAttribute = 'JSXAttribute'; +export type tJSXClosingElement = 'JSXClosingElement'; +export type tJSXClosingFragment = 'JSXClosingFragment'; +export type tJSXElement = 'JSXElement'; +export type tJSXEmptyExpression = 'JSXEmptyExpression'; +export type tJSXExpressionContainer = 'JSXExpressionContainer'; +export type tJSXFragment = 'JSXFragment'; +export type tJSXIdentifier = 'JSXIdentifier'; +export type tJSXMemberExpression = 'JSXMemberExpression'; +export type tJSXNamespacedName = 'JSXNamespacedName'; +export type tJSXOpeningElement = 'JSXOpeningElement'; +export type tJSXOpeningFragment = 'JSXOpeningFragment'; +export type tJSXSpreadAttribute = 'JSXSpreadAttribute'; +export type tJSXSpreadChild = 'JSXSpreadChild'; +export type tJSXText = 'JSXText'; export type tLabeledStatement = 'LabeledStatement'; export type tLiteral = 'Literal'; export type tLogicalExpression = 'LogicalExpression'; @@ -115,6 +130,21 @@ export const ImportDefaultSpecifier: tImportDefaultSpecifier = 'ImportDefaultSpe export const ImportExpression: tImportExpression = 'ImportExpression'; export const ImportNamespaceSpecifier: tImportNamespaceSpecifier = 'ImportNamespaceSpecifier'; export const ImportSpecifier: tImportSpecifier = 'ImportSpecifier'; +export const JSXAttribute: tJSXAttribute = 'JSXAttribute'; +export const JSXClosingElement: tJSXClosingElement = 'JSXClosingElement'; +export const JSXClosingFragment: tJSXClosingFragment = 'JSXClosingFragment'; +export const JSXElement: tJSXElement = 'JSXElement'; +export const JSXEmptyExpression: tJSXEmptyExpression = 'JSXEmptyExpression'; +export const JSXExpressionContainer: tJSXExpressionContainer = 'JSXExpressionContainer'; +export const JSXFragment: tJSXFragment = 'JSXFragment'; +export const JSXIdentifier: tJSXIdentifier = 'JSXIdentifier'; +export const JSXMemberExpression: tJSXMemberExpression = 'JSXMemberExpression'; +export const JSXNamespacedName: tJSXNamespacedName = 'JSXNamespacedName'; +export const JSXOpeningElement: tJSXOpeningElement = 'JSXOpeningElement'; +export const JSXOpeningFragment: tJSXOpeningFragment = 'JSXOpeningFragment'; +export const JSXSpreadAttribute: tJSXSpreadAttribute = 'JSXSpreadAttribute'; +export const JSXSpreadChild: tJSXSpreadChild = 'JSXSpreadChild'; +export const JSXText: tJSXText = 'JSXText'; export const LabeledStatement: tLabeledStatement = 'LabeledStatement'; export const Literal: tLiteral = 'Literal'; export const LogicalExpression: tLogicalExpression = 'LogicalExpression'; diff --git a/src/ast/nodes/index.ts b/src/ast/nodes/index.ts index 56ef603ba67..2ef83573c6c 100644 --- a/src/ast/nodes/index.ts +++ b/src/ast/nodes/index.ts @@ -40,6 +40,21 @@ import ImportDefaultSpecifier from './ImportDefaultSpecifier'; import ImportExpression from './ImportExpression'; import ImportNamespaceSpecifier from './ImportNamespaceSpecifier'; import ImportSpecifier from './ImportSpecifier'; +import JSXAttribute from './JSXAttribute'; +import JSXClosingElement from './JSXClosingElement'; +import JSXClosingFragment from './JSXClosingFragment'; +import JSXElement from './JSXElement'; +import JSXEmptyExpression from './JSXEmptyExpression'; +import JSXExpressionContainer from './JSXExpressionContainer'; +import JSXFragment from './JSXFragment'; +import JSXIdentifier from './JSXIdentifier'; +import JSXMemberExpression from './JSXMemberExpression'; +import JSXNamespacedName from './JSXNamespacedName'; +import JSXOpeningElement from './JSXOpeningElement'; +import JSXOpeningFragment from './JSXOpeningFragment'; +import JSXSpreadAttribute from './JSXSpreadAttribute'; +import JSXSpreadChild from './JSXSpreadChild'; +import JSXText from './JSXText'; import LabeledStatement from './LabeledStatement'; import Literal from './Literal'; import LogicalExpression from './LogicalExpression'; @@ -118,6 +133,21 @@ export const nodeConstructors: Record = { ImportExpression, ImportNamespaceSpecifier, ImportSpecifier, + JSXAttribute, + JSXClosingElement, + JSXClosingFragment, + JSXElement, + JSXEmptyExpression, + JSXExpressionContainer, + JSXFragment, + JSXIdentifier, + JSXMemberExpression, + JSXNamespacedName, + JSXOpeningElement, + JSXOpeningFragment, + JSXSpreadAttribute, + JSXSpreadChild, + JSXText, LabeledStatement, Literal, LogicalExpression, diff --git a/src/ast/nodes/shared/IdentifierBase.ts b/src/ast/nodes/shared/IdentifierBase.ts new file mode 100644 index 00000000000..684d7f2de81 --- /dev/null +++ b/src/ast/nodes/shared/IdentifierBase.ts @@ -0,0 +1,243 @@ +import type { NormalizedTreeshakingOptions } from '../../../rollup/types'; +import { logIllegalImportReassignment } from '../../../utils/logs'; +import { PureFunctionKey } from '../../../utils/pureFunctions'; +import { markModuleAndImpureDependenciesAsExecuted } from '../../../utils/traverseStaticDependencies'; +import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; +import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; +import type { NodeInteraction, NodeInteractionCalled } from '../../NodeInteractions'; +import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, + INTERACTION_CALLED, + NODE_INTERACTION_UNKNOWN_ACCESS +} from '../../NodeInteractions'; +import type { ObjectPath, PathTracker } from '../../utils/PathTracker'; +import { EMPTY_PATH } from '../../utils/PathTracker'; +import GlobalVariable from '../../variables/GlobalVariable'; +import LocalVariable from '../../variables/LocalVariable'; +import type Variable from '../../variables/Variable'; +import type SpreadElement from '../SpreadElement'; +import { Flag, isFlagSet, setFlag } from './BitFlags'; +import type { ExpressionEntity, LiteralValueOrUnknown } from './Expression'; +import { UNKNOWN_EXPRESSION } from './Expression'; +import { NodeBase } from './Node'; + +const tdzVariableKinds = new Set(['class', 'const', 'let', 'var', 'using', 'await using']); + +export default class IdentifierBase extends NodeBase { + name!: string; + variable: Variable | null = null; + + protected isVariableReference = false; + + private get isTDZAccess(): boolean | null { + if (!isFlagSet(this.flags, Flag.tdzAccessDefined)) { + return null; + } + return isFlagSet(this.flags, Flag.tdzAccess); + } + + private set isTDZAccess(value: boolean) { + this.flags = setFlag(this.flags, Flag.tdzAccessDefined, true); + this.flags = setFlag(this.flags, Flag.tdzAccess, value); + } + + deoptimizeArgumentsOnInteractionAtPath( + interaction: NodeInteraction, + path: ObjectPath, + recursionTracker: PathTracker + ): void { + this.variable!.deoptimizeArgumentsOnInteractionAtPath(interaction, path, recursionTracker); + } + + deoptimizePath(path: ObjectPath): void { + if (path.length === 0 && !this.scope.contains(this.name)) { + this.disallowImportReassignment(); + } + // We keep conditional chaining because an unknown Node could have an + // Identifier as property that might be deoptimized by default + this.variable?.deoptimizePath(path); + } + + getLiteralValueAtPath( + path: ObjectPath, + recursionTracker: PathTracker, + origin: DeoptimizableEntity + ): LiteralValueOrUnknown { + return this.getVariableRespectingTDZ()!.getLiteralValueAtPath(path, recursionTracker, origin); + } + + getReturnExpressionWhenCalledAtPath( + path: ObjectPath, + interaction: NodeInteractionCalled, + recursionTracker: PathTracker, + origin: DeoptimizableEntity + ): [expression: ExpressionEntity, isPure: boolean] { + const [expression, isPure] = + this.getVariableRespectingTDZ()!.getReturnExpressionWhenCalledAtPath( + path, + interaction, + recursionTracker, + origin + ); + return [expression, isPure || this.isPureFunction(path)]; + } + + hasEffects(context: HasEffectsContext): boolean { + if (!this.deoptimized) this.applyDeoptimizations(); + if (this.isPossibleTDZ() && this.variable!.kind !== 'var') { + return true; + } + return ( + (this.scope.context.options.treeshake as NormalizedTreeshakingOptions) + .unknownGlobalSideEffects && + this.variable instanceof GlobalVariable && + !this.isPureFunction(EMPTY_PATH) && + this.variable.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + NODE_INTERACTION_UNKNOWN_ACCESS, + context + ) + ); + } + + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteraction, + context: HasEffectsContext + ): boolean { + switch (interaction.type) { + case INTERACTION_ACCESSED: { + return ( + this.variable !== null && + !this.isPureFunction(path) && + this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(path, interaction, context) + ); + } + case INTERACTION_ASSIGNED: { + return ( + path.length > 0 ? this.getVariableRespectingTDZ() : this.variable + )!.hasEffectsOnInteractionAtPath(path, interaction, context); + } + case INTERACTION_CALLED: { + return ( + !this.isPureFunction(path) && + this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(path, interaction, context) + ); + } + } + } + + include(): void { + if (!this.deoptimized) this.applyDeoptimizations(); + if (!this.included) { + this.included = true; + if (this.variable !== null) { + this.scope.context.includeVariableInModule(this.variable); + } + } + } + + includeCallArguments( + context: InclusionContext, + parameters: readonly (ExpressionEntity | SpreadElement)[] + ): void { + this.variable!.includeCallArguments(context, parameters); + } + + isPossibleTDZ(): boolean { + // return cached value to avoid issues with the next tree-shaking pass + const cachedTdzAccess = this.isTDZAccess; + if (cachedTdzAccess !== null) return cachedTdzAccess; + + if ( + !( + this.variable instanceof LocalVariable && + this.variable.kind && + tdzVariableKinds.has(this.variable.kind) && + // We ignore modules that did not receive a treeshaking pass yet as that + // causes many false positives due to circular dependencies or disabled + // moduleSideEffects. + this.variable.module.hasTreeShakingPassStarted + ) + ) { + return (this.isTDZAccess = false); + } + + let decl_id; + if ( + this.variable.declarations && + this.variable.declarations.length === 1 && + (decl_id = this.variable.declarations[0] as any) && + this.start < decl_id.start && + closestParentFunctionOrProgram(this) === closestParentFunctionOrProgram(decl_id) + ) { + // a variable accessed before its declaration + // in the same function or at top level of module + return (this.isTDZAccess = true); + } + + if (!this.variable.initReached) { + // Either a const/let TDZ violation or + // var use before declaration was encountered. + return (this.isTDZAccess = true); + } + + return (this.isTDZAccess = false); + } + + protected applyDeoptimizations(): void { + this.deoptimized = true; + if (this.variable instanceof LocalVariable) { + // When accessing a variable from a module without side effects, this + // means we use an export of that module and therefore need to potentially + // include it in the bundle. + if (!this.variable.module.isExecuted) { + markModuleAndImpureDependenciesAsExecuted(this.variable.module); + } + this.variable.consolidateInitializers(); + this.scope.context.requestTreeshakingPass(); + } + if (this.isVariableReference) { + this.variable!.addUsedPlace(this); + this.scope.context.requestTreeshakingPass(); + } + } + + private disallowImportReassignment(): never { + return this.scope.context.error( + logIllegalImportReassignment(this.name, this.scope.context.module.id), + this.start + ); + } + + private getVariableRespectingTDZ(): ExpressionEntity | null { + if (this.isPossibleTDZ()) { + return UNKNOWN_EXPRESSION; + } + return this.variable; + } + + private isPureFunction(path: ObjectPath) { + let currentPureFunction = this.scope.context.manualPureFunctions[this.name]; + for (const segment of path) { + if (currentPureFunction) { + if (currentPureFunction[PureFunctionKey]) { + return true; + } + currentPureFunction = currentPureFunction[segment as string]; + } else { + return false; + } + } + return currentPureFunction?.[PureFunctionKey] as boolean; + } +} + +function closestParentFunctionOrProgram(node: any): any { + while (node && !/^Program|Function/.test(node.type)) { + node = node.parent; + } + // one of: ArrowFunctionExpression, FunctionDeclaration, FunctionExpression or Program + return node; +} diff --git a/src/ast/nodes/shared/JSXClosingBase.ts b/src/ast/nodes/shared/JSXClosingBase.ts new file mode 100644 index 00000000000..9cb69e19bd7 --- /dev/null +++ b/src/ast/nodes/shared/JSXClosingBase.ts @@ -0,0 +1,15 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../../rollup/types'; +import type { RenderOptions } from '../../../utils/renderHelpers'; +import { NodeBase } from './Node'; + +export default class JSXClosingBase extends NodeBase { + render(code: MagicString, options: RenderOptions): void { + const { mode } = this.scope.context.options.jsx as NormalizedJsxOptions; + if (mode !== 'preserve') { + code.overwrite(this.start, this.end, ')', { contentOnly: true }); + } else { + super.render(code, options); + } + } +} diff --git a/src/ast/nodes/shared/JSXElementBase.ts b/src/ast/nodes/shared/JSXElementBase.ts new file mode 100644 index 00000000000..6f8b08e797d --- /dev/null +++ b/src/ast/nodes/shared/JSXElementBase.ts @@ -0,0 +1,84 @@ +import type MagicString from 'magic-string'; +import type { NormalizedJsxOptions } from '../../../rollup/types'; +import { getRenderedJsxChildren } from '../../../utils/jsx'; +import type { RenderOptions } from '../../../utils/renderHelpers'; +import type { InclusionContext } from '../../ExecutionContext'; +import type Variable from '../../variables/Variable'; +import JSXEmptyExpression from '../JSXEmptyExpression'; +import JSXExpressionContainer from '../JSXExpressionContainer'; +import type { JSXChild, JsxMode } from './jsxHelpers'; +import { getAndIncludeFactoryVariable } from './jsxHelpers'; +import type { IncludeChildren } from './Node'; +import { NodeBase } from './Node'; + +export default class JSXElementBase extends NodeBase { + children!: JSXChild[]; + + protected factoryVariable: Variable | null = null; + protected factory: string | null = null; + protected declare jsxMode: JsxMode; + + initialise() { + super.initialise(); + const { importSource } = (this.jsxMode = this.getRenderingMode()); + if (importSource) { + this.scope.context.addImportSource(importSource); + } + } + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + if (!this.included) { + const { factory, importSource, mode } = this.jsxMode; + if (factory) { + this.factory = factory; + this.factoryVariable = getAndIncludeFactoryVariable( + factory, + mode === 'preserve', + importSource, + this + ); + } + } + super.include(context, includeChildrenRecursively); + } + + protected applyDeoptimizations() {} + + protected getRenderingMode(): JsxMode { + const jsx = this.scope.context.options.jsx as NormalizedJsxOptions; + const { mode, factory, importSource } = jsx; + if (mode === 'automatic') { + return { + factory: getRenderedJsxChildren(this.children) > 1 ? 'jsxs' : 'jsx', + importSource: jsx.jsxImportSource, + mode + }; + } + return { factory, importSource, mode }; + } + + protected renderChildren(code: MagicString, options: RenderOptions, openingEnd: number) { + const { children } = this; + let hasMultipleChildren = false; + let childrenEnd = openingEnd; + let firstChild: JSXChild | null = null; + for (const child of children) { + if ( + child instanceof JSXExpressionContainer && + child.expression instanceof JSXEmptyExpression + ) { + code.remove(childrenEnd, child.end); + } else { + code.appendLeft(childrenEnd, ', '); + child.render(code, options); + if (firstChild) { + hasMultipleChildren = true; + } else { + firstChild = child; + } + } + childrenEnd = child.end; + } + return { childrenEnd, firstChild, hasMultipleChildren }; + } +} diff --git a/src/ast/nodes/shared/jsxHelpers.ts b/src/ast/nodes/shared/jsxHelpers.ts new file mode 100644 index 00000000000..bdbdfe8ef20 --- /dev/null +++ b/src/ast/nodes/shared/jsxHelpers.ts @@ -0,0 +1,52 @@ +import LocalVariable from '../../variables/LocalVariable'; +import type Variable from '../../variables/Variable'; +import type JSXElement from '../JSXElement'; +import type JSXExpressionContainer from '../JSXExpressionContainer'; +import type JSXFragment from '../JSXFragment'; +import type JSXOpeningElement from '../JSXOpeningElement'; +import type JSXOpeningFragment from '../JSXOpeningFragment'; +import type JSXSpreadChild from '../JSXSpreadChild'; +import type JSXText from '../JSXText'; +import type JSXElementBase from './JSXElementBase'; + +export type JsxMode = + | { + mode: 'preserve' | 'classic'; + factory: string | null; + importSource: string | null; + } + | { mode: 'automatic'; factory: string; importSource: string }; +export type JSXChild = JSXText | JSXExpressionContainer | JSXElement | JSXFragment | JSXSpreadChild; + +export function getAndIncludeFactoryVariable( + factory: string, + preserve: boolean, + importSource: string | null, + node: JSXElementBase | JSXOpeningElement | JSXOpeningFragment +): Variable { + const [baseName, nestedName] = factory.split('.'); + let factoryVariable: Variable; + if (importSource) { + factoryVariable = node.scope.context.getImportedJsxFactoryVariable( + nestedName ? 'default' : baseName, + node.start, + importSource + ); + if (preserve) { + // This pretends we are accessing an included global variable of the same name + const globalVariable = node.scope.findGlobal(baseName); + globalVariable.include(); + // This excludes this variable from renaming + factoryVariable.globalName = baseName; + } + } else { + factoryVariable = node.scope.findGlobal(baseName); + } + node.scope.context.includeVariableInModule(factoryVariable); + if (factoryVariable instanceof LocalVariable) { + factoryVariable.consolidateInitializers(); + factoryVariable.addUsedPlace(node); + node.scope.context.requestTreeshakingPass(); + } + return factoryVariable; +} diff --git a/src/ast/scopes/ChildScope.ts b/src/ast/scopes/ChildScope.ts index d13f83af4e2..50d6afa271d 100644 --- a/src/ast/scopes/ChildScope.ts +++ b/src/ast/scopes/ChildScope.ts @@ -106,6 +106,12 @@ export default class ChildScope extends Scope { return (this.parent as ChildScope).findLexicalBoundary(); } + findGlobal(name: string): Variable { + const variable = this.parent.findVariable(name); + this.accessedOutsideVariables.set(name, variable); + return variable; + } + findVariable(name: string): Variable { const knownVariable = this.variables.get(name) || this.accessedOutsideVariables.get(name); if (knownVariable) { diff --git a/src/ast/variables/ExportDefaultVariable.ts b/src/ast/variables/ExportDefaultVariable.ts index 70810936e5a..baed0873c12 100644 --- a/src/ast/variables/ExportDefaultVariable.ts +++ b/src/ast/variables/ExportDefaultVariable.ts @@ -3,6 +3,7 @@ import ClassDeclaration from '../nodes/ClassDeclaration'; import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import FunctionDeclaration from '../nodes/FunctionDeclaration'; import Identifier, { type IdentifierWithVariable } from '../nodes/Identifier'; +import type IdentifierBase from '../nodes/shared/IdentifierBase'; import type { NodeBase } from '../nodes/shared/Node'; import LocalVariable from './LocalVariable'; import UndefinedVariable from './UndefinedVariable'; @@ -32,7 +33,7 @@ export default class ExportDefaultVariable extends LocalVariable { } } - addReference(identifier: Identifier): void { + addReference(identifier: IdentifierBase): void { if (!this.hasId) { this.name = identifier.name; } diff --git a/src/ast/variables/ExternalVariable.ts b/src/ast/variables/ExternalVariable.ts index 529802a7ce7..0775fc03682 100644 --- a/src/ast/variables/ExternalVariable.ts +++ b/src/ast/variables/ExternalVariable.ts @@ -1,7 +1,7 @@ import type ExternalModule from '../../ExternalModule'; import type { NodeInteraction } from '../NodeInteractions'; import { INTERACTION_ACCESSED } from '../NodeInteractions'; -import type Identifier from '../nodes/Identifier'; +import type IdentifierBase from '../nodes/shared/IdentifierBase'; import type { ObjectPath } from '../utils/PathTracker'; import Variable from './Variable'; @@ -16,7 +16,7 @@ export default class ExternalVariable extends Variable { this.isNamespace = name === '*'; } - addReference(identifier: Identifier): void { + addReference(identifier: IdentifierBase): void { this.referenced = true; if (this.name === 'default' || this.name === '*') { this.module.suggestName(identifier.name); diff --git a/src/ast/variables/NamespaceVariable.ts b/src/ast/variables/NamespaceVariable.ts index dc5efceea89..6085c435b76 100644 --- a/src/ast/variables/NamespaceVariable.ts +++ b/src/ast/variables/NamespaceVariable.ts @@ -6,9 +6,9 @@ import { getSystemExportStatement } from '../../utils/systemJsRendering'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteraction } from '../NodeInteractions'; import { INTERACTION_ASSIGNED, INTERACTION_CALLED } from '../NodeInteractions'; -import type Identifier from '../nodes/Identifier'; import type { LiteralValueOrUnknown } from '../nodes/shared/Expression'; import { deoptimizeInteraction, UnknownValue } from '../nodes/shared/Expression'; +import type IdentifierBase from '../nodes/shared/IdentifierBase'; import type ChildScope from '../scopes/ChildScope'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import { SymbolToStringTag } from '../utils/PathTracker'; @@ -22,7 +22,7 @@ export default class NamespaceVariable extends Variable { private memberVariables: Record | null = null; private mergedNamespaces: readonly Variable[] = []; private referencedEarly = false; - private references: Identifier[] = []; + private references: IdentifierBase[] = []; constructor(context: AstContext) { super(context.getModuleName()); @@ -30,7 +30,7 @@ export default class NamespaceVariable extends Variable { this.module = context.module; } - addReference(identifier: Identifier): void { + addReference(identifier: IdentifierBase): void { this.references.push(identifier); this.name = identifier.name; } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 0cd2e40dddb..2b8a1924f47 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -5,9 +5,9 @@ import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteraction } from '../NodeInteractions'; import { INTERACTION_ACCESSED } from '../NodeInteractions'; import type CallExpression from '../nodes/CallExpression'; -import type Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; import { ExpressionEntity } from '../nodes/shared/Expression'; +import type IdentifierBase from '../nodes/shared/IdentifierBase'; import type { NodeBase } from '../nodes/shared/Node'; import type { VariableKind } from '../nodes/shared/VariableKinds'; import type { ObjectPath } from '../utils/PathTracker'; @@ -15,6 +15,7 @@ import type { ObjectPath } from '../utils/PathTracker'; export default class Variable extends ExpressionEntity { alwaysRendered = false; forbiddenNames: Set | null = null; + globalName: string | null = null; initReached = false; isId = false; // both NamespaceVariable and ExternalVariable can be namespaces @@ -39,7 +40,7 @@ export default class Variable extends ExpressionEntity { * Binds identifiers that reference this variable to this variable. * Necessary to be able to change variable names. */ - addReference(_identifier: Identifier): void {} + addReference(_identifier: IdentifierBase): void {} private onlyFunctionCallUsed = true; /** @@ -84,6 +85,9 @@ export default class Variable extends ExpressionEntity { getPropertyAccess: (name: string) => string, useOriginalName?: RenderOptions['useOriginalName'] ): string { + if (this.globalName) { + return this.globalName; + } if (useOriginalName?.(this)) { return this.name; } @@ -103,10 +107,10 @@ export default class Variable extends ExpressionEntity { } /** - * Marks this variable as being part of the bundle, which is usually the case when one of - * its identifiers becomes part of the bundle. Returns true if it has not been included - * previously. - * Once a variable is included, it should take care all its declarations are included. + * Marks this variable as being part of the bundle, which is usually the case + * when one of its identifiers becomes part of the bundle. Returns true if it + * has not been included previously. Once a variable is included, it should + * take care all its declarations are included. */ include(): void { this.included = true; diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 20a0488dd78..8dfbd7eb6d4 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -220,7 +220,7 @@ type LoggingFunctionWithPosition = ( export type ParseAst = ( input: string, - options?: { allowReturnOutsideFunction?: boolean } + options?: { allowReturnOutsideFunction?: boolean; jsx?: boolean } ) => ProgramNode; // declare AbortSignal here for environments without DOM lib or @types/node @@ -231,7 +231,7 @@ declare global { export type ParseAstAsync = ( input: string, - options?: { allowReturnOutsideFunction?: boolean; signal?: AbortSignal } + options?: { allowReturnOutsideFunction?: boolean; jsx?: boolean; signal?: AbortSignal } ) => Promise; export interface PluginContext extends MinimalPluginContext { @@ -523,6 +523,38 @@ export interface Plugin extends OutputPlugin, Partial { api?: A; } +export type JsxPreset = 'react' | 'react-jsx' | 'preserve' | 'preserve-react'; + +export type NormalizedJsxOptions = + | NormalizedJsxPreserveOptions + | NormalizedJsxClassicOptions + | NormalizedJsxAutomaticOptions; + +interface NormalizedJsxPreserveOptions { + factory: string | null; + fragment: string | null; + importSource: string | null; + mode: 'preserve'; +} + +interface NormalizedJsxClassicOptions { + factory: string; + fragment: string; + importSource: string | null; + mode: 'classic'; +} + +interface NormalizedJsxAutomaticOptions { + factory: string; + importSource: string | null; + jsxImportSource: string; + mode: 'automatic'; +} + +export type JsxOptions = Partial & { + preset?: JsxPreset; +}; + export type TreeshakingPreset = 'smallest' | 'safest' | 'recommended'; export interface NormalizedTreeshakingOptions { @@ -545,6 +577,7 @@ interface ManualChunkMeta { getModuleIds: () => IterableIterator; getModuleInfo: GetModuleInfo; } + export type GetManualChunk = (id: string, meta: ManualChunkMeta) => string | NullValue; export type ExternalOption = @@ -592,6 +625,7 @@ export interface InputOptions { experimentalLogSideEffects?: boolean; external?: ExternalOption; input?: InputOption; + jsx?: false | JsxPreset | JsxOptions; logLevel?: LogLevelOption; makeAbsoluteExternalsRelative?: boolean | 'ifRelativeSource'; maxParallelFileOps?: number; @@ -619,6 +653,7 @@ export interface NormalizedInputOptions { experimentalLogSideEffects: boolean; external: IsExternal; input: string[] | Record; + jsx: false | NormalizedJsxOptions; logLevel: LogLevelOption; makeAbsoluteExternalsRelative: boolean | 'ifRelativeSource'; maxParallelFileOps: number; diff --git a/src/utils/bufferToAst.ts b/src/utils/bufferToAst.ts index bf870cc1d5e..20523a842da 100644 --- a/src/utils/bufferToAst.ts +++ b/src/utils/bufferToAst.ts @@ -450,6 +450,139 @@ const nodeConverters: ((position: number, buffer: AstBuffer) => any)[] = [ local }; }, + function jsxAttribute(position, buffer): JSXAttributeNode { + const valuePosition = buffer[position + 3]; + return { + type: 'JSXAttribute', + start: buffer[position], + end: buffer[position + 1], + name: convertNode(buffer[position + 2], buffer), + value: valuePosition === 0 ? null : convertNode(valuePosition, buffer) + }; + }, + function jsxClosingElement(position, buffer): JSXClosingElementNode { + return { + type: 'JSXClosingElement', + start: buffer[position], + end: buffer[position + 1], + name: convertNode(buffer[position + 2], buffer) + }; + }, + function jsxClosingFragment(position, buffer): JSXClosingFragmentNode { + return { + type: 'JSXClosingFragment', + start: buffer[position], + end: buffer[position + 1] + }; + }, + function jsxElement(position, buffer): JSXElementNode { + const closingElementPosition = buffer[position + 4]; + return { + type: 'JSXElement', + start: buffer[position], + end: buffer[position + 1], + openingElement: convertNode(buffer[position + 2], buffer), + children: convertNodeList(buffer[position + 3], buffer), + closingElement: + closingElementPosition === 0 ? null : convertNode(closingElementPosition, buffer) + }; + }, + function jsxEmptyExpression(position, buffer): JSXEmptyExpressionNode { + return { + type: 'JSXEmptyExpression', + start: buffer[position], + end: buffer[position + 1] + }; + }, + function jsxExpressionContainer(position, buffer): JSXExpressionContainerNode { + return { + type: 'JSXExpressionContainer', + start: buffer[position], + end: buffer[position + 1], + expression: convertNode(buffer[position + 2], buffer) + }; + }, + function jsxFragment(position, buffer): JSXFragmentNode { + return { + type: 'JSXFragment', + start: buffer[position], + end: buffer[position + 1], + openingFragment: convertNode(buffer[position + 2], buffer), + children: convertNodeList(buffer[position + 3], buffer), + closingFragment: convertNode(buffer[position + 4], buffer) + }; + }, + function jsxIdentifier(position, buffer): JSXIdentifierNode { + return { + type: 'JSXIdentifier', + start: buffer[position], + end: buffer[position + 1], + name: buffer.convertString(buffer[position + 2]) + }; + }, + function jsxMemberExpression(position, buffer): JSXMemberExpressionNode { + return { + type: 'JSXMemberExpression', + start: buffer[position], + end: buffer[position + 1], + object: convertNode(buffer[position + 2], buffer), + property: convertNode(buffer[position + 3], buffer) + }; + }, + function jsxNamespacedName(position, buffer): JSXNamespacedNameNode { + return { + type: 'JSXNamespacedName', + start: buffer[position], + end: buffer[position + 1], + namespace: convertNode(buffer[position + 2], buffer), + name: convertNode(buffer[position + 3], buffer) + }; + }, + function jsxOpeningElement(position, buffer): JSXOpeningElementNode { + const flags = buffer[position + 2]; + return { + type: 'JSXOpeningElement', + start: buffer[position], + end: buffer[position + 1], + selfClosing: (flags & 1) === 1, + name: convertNode(buffer[position + 3], buffer), + attributes: convertNodeList(buffer[position + 4], buffer) + }; + }, + function jsxOpeningFragment(position, buffer): JSXOpeningFragmentNode { + return { + type: 'JSXOpeningFragment', + start: buffer[position], + end: buffer[position + 1], + attributes: [], + selfClosing: false + }; + }, + function jsxSpreadAttribute(position, buffer): JSXSpreadAttributeNode { + return { + type: 'JSXSpreadAttribute', + start: buffer[position], + end: buffer[position + 1], + argument: convertNode(buffer[position + 2], buffer) + }; + }, + function jsxSpreadChild(position, buffer): JSXSpreadChildNode { + return { + type: 'JSXSpreadChild', + start: buffer[position], + end: buffer[position + 1], + expression: convertNode(buffer[position + 2], buffer) + }; + }, + function jsxText(position, buffer): JSXTextNode { + return { + type: 'JSXText', + start: buffer[position], + end: buffer[position + 1], + value: buffer.convertString(buffer[position + 2]), + raw: buffer.convertString(buffer[position + 3]) + }; + }, function labeledStatement(position, buffer): LabeledStatementNode { return { type: 'LabeledStatement', @@ -895,6 +1028,21 @@ export type ImportExpressionNode = RollupAstNode< >; export type ImportNamespaceSpecifierNode = RollupAstNode; export type ImportSpecifierNode = RollupAstNode; +export type JSXAttributeNode = RollupAstNode; +export type JSXClosingElementNode = RollupAstNode; +export type JSXClosingFragmentNode = RollupAstNode; +export type JSXElementNode = RollupAstNode; +export type JSXEmptyExpressionNode = RollupAstNode; +export type JSXExpressionContainerNode = RollupAstNode; +export type JSXFragmentNode = RollupAstNode; +export type JSXIdentifierNode = RollupAstNode; +export type JSXMemberExpressionNode = RollupAstNode; +export type JSXNamespacedNameNode = RollupAstNode; +export type JSXOpeningElementNode = RollupAstNode; +export type JSXOpeningFragmentNode = RollupAstNode; +export type JSXSpreadAttributeNode = RollupAstNode; +export type JSXSpreadChildNode = RollupAstNode; +export type JSXTextNode = RollupAstNode; export type LabeledStatementNode = RollupAstNode; export type LiteralBigIntNode = RollupAstNode; export type LiteralBooleanNode = RollupAstNode; diff --git a/src/utils/jsx.ts b/src/utils/jsx.ts new file mode 100644 index 00000000000..978cc0ba2a4 --- /dev/null +++ b/src/utils/jsx.ts @@ -0,0 +1,20 @@ +import type JSXElement from '../ast/nodes/JSXElement'; +import JSXEmptyExpression from '../ast/nodes/JSXEmptyExpression'; +import JSXExpressionContainer from '../ast/nodes/JSXExpressionContainer'; +import type JSXFragment from '../ast/nodes/JSXFragment'; +import type JSXSpreadChild from '../ast/nodes/JSXSpreadChild'; +import type JSXText from '../ast/nodes/JSXText'; + +export function getRenderedJsxChildren( + children: (JSXText | JSXExpressionContainer | JSXElement | JSXFragment | JSXSpreadChild)[] +) { + let renderedChildren = 0; + for (const child of children) { + if ( + !(child instanceof JSXExpressionContainer && child.expression instanceof JSXEmptyExpression) + ) { + renderedChildren++; + } + } + return renderedChildren; +} diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 2274103f6e8..7c09d04a16f 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -18,6 +18,7 @@ import { URL_AVOIDING_EVAL, URL_BUNDLE_CONFIG_AS_CJS, URL_CONFIGURATION_FILES, + URL_JSX, URL_NAME_IS_NOT_EXPORTED, URL_OUTPUT_DIR, URL_OUTPUT_EXPORTS, @@ -154,6 +155,7 @@ const ADDON_ERROR = 'ADDON_ERROR', MISSING_EXTERNAL_CONFIG = 'MISSING_EXTERNAL_CONFIG', MISSING_GLOBAL_NAME = 'MISSING_GLOBAL_NAME', MISSING_IMPLICIT_DEPENDANT = 'MISSING_IMPLICIT_DEPENDANT', + MISSING_JSX_EXPORT = 'MISSING_JSX_EXPORT', MISSING_NAME_OPTION_FOR_IIFE_EXPORT = 'MISSING_NAME_OPTION_FOR_IIFE_EXPORT', MISSING_NODE_BUILTINS = 'MISSING_NODE_BUILTINS', MISSING_OPTION = 'MISSING_OPTION', @@ -770,6 +772,17 @@ export function logImplicitDependantIsNotIncluded(module: Module): RollupLog { }; } +export function logMissingJsxExport(name: string, exporter: string, importer: string): RollupLog { + return { + code: MISSING_JSX_EXPORT, + exporter, + id: importer, + message: `Export "${name}" is not defined in module "${relativeId(exporter)}" even though it is needed in "${relativeId(importer)}" to provide JSX syntax. Please check your "jsx" option.`, + names: [name], + url: getRollupUrl(URL_JSX) + }; +} + export function logMissingNameOptionForIifeExport(): RollupLog { return { code: MISSING_NAME_OPTION_FOR_IIFE_EXPORT, diff --git a/src/utils/options/mergeOptions.ts b/src/utils/options/mergeOptions.ts index 0e78b2374bb..29d7ff0e842 100644 --- a/src/utils/options/mergeOptions.ts +++ b/src/utils/options/mergeOptions.ts @@ -11,12 +11,13 @@ import type { import { ensureArray } from '../ensureArray'; import { getLogger } from '../logger'; import { LOGLEVEL_INFO } from '../logging'; -import { URL_OUTPUT_GENERATEDCODE, URL_TREESHAKE } from '../urls'; +import { URL_JSX, URL_OUTPUT_GENERATEDCODE, URL_TREESHAKE } from '../urls'; import type { CommandConfigObject } from './normalizeInputOptions'; import { generatedCodePresets, type GenericConfigObject, getOnLog, + jsxPresets, normalizePluginOption, objectifyOption, objectifyOptionWithPresets, @@ -134,6 +135,12 @@ function mergeInputOptions( experimentalLogSideEffects: getOption('experimentalLogSideEffects'), external: getExternal(config, overrides), input: getOption('input') || [], + jsx: getObjectOption( + config, + overrides, + 'jsx', + objectifyOptionWithPresets(jsxPresets, 'jsx', URL_JSX, 'false, ') + ), logLevel: getOption('logLevel'), makeAbsoluteExternalsRelative: getOption('makeAbsoluteExternalsRelative'), maxParallelFileOps: getOption('maxParallelFileOps'), @@ -172,7 +179,7 @@ const getObjectOption = ( overrides: T, name: keyof T, objectifyValue = objectifyOption -) => { +): any => { const commandOption = normalizeObjectOptionValue(overrides[name], objectifyValue); const configOption = normalizeObjectOptionValue(config[name], objectifyValue); if (commandOption !== undefined) { diff --git a/src/utils/options/normalizeInputOptions.ts b/src/utils/options/normalizeInputOptions.ts index 0342b71d801..85127200c0e 100644 --- a/src/utils/options/normalizeInputOptions.ts +++ b/src/utils/options/normalizeInputOptions.ts @@ -11,10 +11,11 @@ import { getLogger } from '../logger'; import { LOGLEVEL_INFO } from '../logging'; import { error, logInvalidOption } from '../logs'; import { resolve } from '../path'; -import { URL_TREESHAKE, URL_TREESHAKE_MODULESIDEEFFECTS } from '../urls'; +import { URL_JSX, URL_TREESHAKE, URL_TREESHAKE_MODULESIDEEFFECTS } from '../urls'; import { getOnLog, getOptionWithPreset, + jsxPresets, normalizePluginOption, treeshakePresets, warnUnknownOptions @@ -50,6 +51,7 @@ export async function normalizeInputOptions( experimentalLogSideEffects: config.experimentalLogSideEffects || false, external: getIdMatcher(config.external), input: getInput(config), + jsx: getJsx(config), logLevel, makeAbsoluteExternalsRelative: config.makeAbsoluteExternalsRelative ?? 'ifRelativeSource', maxParallelFileOps, @@ -114,6 +116,59 @@ const getInput = (config: InputOptions): NormalizedInputOptions['input'] => { return configInput == null ? [] : typeof configInput === 'string' ? [configInput] : configInput; }; +const getJsx = (config: InputOptions): NormalizedInputOptions['jsx'] => { + const configJsx = config.jsx; + if (!configJsx) return false; + const configWithPreset = getOptionWithPreset(configJsx, jsxPresets, 'jsx', URL_JSX, 'false, '); + const { factory, importSource, mode } = configWithPreset; + switch (mode) { + case 'automatic': { + return { + factory: factory || 'React.createElement', + importSource: importSource || 'react', + jsxImportSource: configWithPreset.jsxImportSource || 'react/jsx-runtime', + mode: 'automatic' + }; + } + case 'preserve': { + if (importSource && !(factory || configWithPreset.fragment)) { + error( + logInvalidOption( + 'jsx', + URL_JSX, + 'when preserving JSX and specifying an importSource, you also need to specify a factory or fragment' + ) + ); + } + return { + factory: factory || null, + fragment: configWithPreset.fragment || null, + importSource: importSource || null, + mode: 'preserve' + }; + } + // case 'classic': + default: { + if (mode && mode !== 'classic') { + error( + logInvalidOption( + 'jsx.mode', + URL_JSX, + 'mode must be "automatic", "classic" or "preserve"', + mode + ) + ); + } + return { + factory: factory || 'React.createElement', + fragment: configWithPreset.fragment || 'React.Fragment', + importSource: importSource || null, + mode: 'classic' + }; + } + } +}; + const getMaxParallelFileOps = ( config: InputOptions ): NormalizedInputOptions['maxParallelFileOps'] => { diff --git a/src/utils/options/options.ts b/src/utils/options/options.ts index 2fe3cea1af2..a459e4dfeea 100644 --- a/src/utils/options/options.ts +++ b/src/utils/options/options.ts @@ -5,6 +5,7 @@ import type { LogLevelOption, NormalizedGeneratedCodeOptions, NormalizedInputOptions, + NormalizedJsxOptions, NormalizedOutputOptions, NormalizedTreeshakingOptions, OutputOptions, @@ -136,6 +137,35 @@ export const treeshakePresets: { } }; +export const jsxPresets: { + [key in NonNullable['preset']>]: NormalizedJsxOptions; +} = { + preserve: { + factory: null, + fragment: null, + importSource: null, + mode: 'preserve' + }, + 'preserve-react': { + factory: 'React.createElement', + fragment: 'React.Fragment', + importSource: 'react', + mode: 'preserve' + }, + react: { + factory: 'React.createElement', + fragment: 'React.Fragment', + importSource: 'react', + mode: 'classic' + }, + 'react-jsx': { + factory: 'React.createElement', + importSource: 'react', + jsxImportSource: 'react/jsx-runtime', + mode: 'automatic' + } +}; + export const generatedCodePresets: { [key in NonNullable< ObjectValue['preset'] @@ -159,7 +189,8 @@ export const generatedCodePresets: { type ObjectOptionWithPresets = | Partial - | Partial; + | Partial + | Partial; export const objectifyOption = (value: unknown): Record => value && typeof value === 'object' ? (value as Record) : {}; @@ -197,7 +228,7 @@ export const getOptionWithPreset = ( optionName: string, urlSnippet: string, additionalValues: string -): Record => { +): T => { const presetName: string | undefined = (value as any)?.preset; if (presetName) { const preset = presets[presetName]; @@ -214,7 +245,7 @@ export const getOptionWithPreset = ( ); } } - return objectifyOptionWithPresets(presets, optionName, urlSnippet, additionalValues)(value); + return objectifyOptionWithPresets(presets, optionName, urlSnippet, additionalValues)(value) as T; }; export const normalizePluginOption: { diff --git a/src/utils/parseAst.ts b/src/utils/parseAst.ts index 75e90c6f065..4300ad847d3 100644 --- a/src/utils/parseAst.ts +++ b/src/utils/parseAst.ts @@ -3,10 +3,12 @@ import type { ParseAst, ParseAstAsync } from '../rollup/types'; import { convertProgram } from './bufferToAst'; import { getAstBuffer } from './getAstBuffer'; -export const parseAst: ParseAst = (input, { allowReturnOutsideFunction = false } = {}) => - convertProgram(getAstBuffer(parse(input, allowReturnOutsideFunction))); +export const parseAst: ParseAst = ( + input, + { allowReturnOutsideFunction = false, jsx = false } = {} +) => convertProgram(getAstBuffer(parse(input, allowReturnOutsideFunction, jsx))); export const parseAstAsync: ParseAstAsync = async ( input, - { allowReturnOutsideFunction = false, signal } = {} -) => convertProgram(getAstBuffer(await parseAsync(input, allowReturnOutsideFunction, signal))); + { allowReturnOutsideFunction = false, jsx = false, signal } = {} +) => convertProgram(getAstBuffer(await parseAsync(input, allowReturnOutsideFunction, jsx, signal))); diff --git a/src/utils/parseImportAttributes.ts b/src/utils/parseImportAttributes.ts index 2ec6fe227d7..0775a8ab02f 100644 --- a/src/utils/parseImportAttributes.ts +++ b/src/utils/parseImportAttributes.ts @@ -65,7 +65,9 @@ const getPropertyKey = (property: Property | SpreadElement | ImportAttribute): L ); }; -export function getAttributesFromImportExportDeclaration(attributes: ImportAttribute[]) { +export function getAttributesFromImportExportDeclaration( + attributes: ImportAttribute[] +): Record { return attributes?.length ? Object.fromEntries( attributes.map(assertion => [getPropertyKey(assertion), assertion.value.value]) diff --git a/src/utils/renderHelpers.ts b/src/utils/renderHelpers.ts index e0b2c2fc1c3..28613d3dfbb 100644 --- a/src/utils/renderHelpers.ts +++ b/src/utils/renderHelpers.ts @@ -2,8 +2,8 @@ import type MagicString from 'magic-string'; import type { Node, StatementNode } from '../ast/nodes/shared/Node'; import type Variable from '../ast/variables/Variable'; import type { InternalModuleFormat } from '../rollup/types'; -import type { PluginDriver } from './PluginDriver'; import type { GenerateCodeSnippets } from './generateCodeSnippets'; +import type { PluginDriver } from './PluginDriver'; import { treeshakeNode } from './treeshakeNode'; export interface RenderOptions { @@ -23,6 +23,7 @@ export interface NodeRenderOptions { isCalleeOfRenderedParent?: boolean; isNoStatement?: boolean; isShorthandProperty?: boolean; + jsxMode?: 'preserve' | 'classic' | 'automatic'; preventASI?: boolean; /* Indicates if the direct parent of an element changed. Necessary for determining the "this" context of callees. */ diff --git a/src/utils/urls.ts b/src/utils/urls.ts index 13dc372c49b..3b94e774b83 100644 --- a/src/utils/urls.ts +++ b/src/utils/urls.ts @@ -8,6 +8,7 @@ export const URL_SOURCEMAP_IS_LIKELY_TO_BE_INCORRECT = 'troubleshooting/#warning-sourcemap-is-likely-to-be-incorrect'; // configuration-options +export const URL_JSX = 'configuration-options/#jsx'; export const URL_MAXPARALLELFILEOPS = 'configuration-options/#maxparallelfileops'; export const URL_OUTPUT_AMD_ID = 'configuration-options/#output-amd-id'; export const URL_OUTPUT_AMD_BASEPATH = 'configuration-options/#output-amd-basepath'; diff --git a/test/cli/samples/jsx/_config.js b/test/cli/samples/jsx/_config.js new file mode 100644 index 00000000000..a837513c062 --- /dev/null +++ b/test/cli/samples/jsx/_config.js @@ -0,0 +1,4 @@ +module.exports = defineTest({ + description: 'supports jsx presets via CLI', + command: 'rollup -i main.js --jsx react --external react' +}); diff --git a/test/cli/samples/jsx/_expected.js b/test/cli/samples/jsx/_expected.js new file mode 100644 index 00000000000..abf18f99bc1 --- /dev/null +++ b/test/cli/samples/jsx/_expected.js @@ -0,0 +1,3 @@ +import react from 'react'; + +console.log(/*#__PURE__*/react.createElement("div", null, "Hello, world!")); diff --git a/test/cli/samples/jsx/main.js b/test/cli/samples/jsx/main.js new file mode 100644 index 00000000000..877d1e6d90b --- /dev/null +++ b/test/cli/samples/jsx/main.js @@ -0,0 +1,2 @@ +import React from 'react'; +console.log(
Hello, world!
); diff --git a/test/form/samples/jsx/preserves-jsx-attributes/_config.js b/test/form/samples/jsx/preserves-jsx-attributes/_config.js new file mode 100644 index 00000000000..741ed769f87 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-attributes/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX with string attributes output', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-attributes/_expected.js b/test/form/samples/jsx/preserves-jsx-attributes/_expected.js new file mode 100644 index 00000000000..652f261869c --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-attributes/_expected.js @@ -0,0 +1,21 @@ +const Foo$2 = () => {}; +const value$2 = 'value 1'; +console.log(Foo$2, value$2); + +const Foo$1 = () => {}; +const value$1 = 'value 2'; +console.log(Foo$1, value$1); + +const Foo = () => {}; +const value = 'value 3'; +console.log(Foo, value); + +const result = + fragment=<> +/>; + +export { result }; diff --git a/test/form/samples/jsx/preserves-jsx-attributes/dep1.js b/test/form/samples/jsx/preserves-jsx-attributes/dep1.js new file mode 100644 index 00000000000..7e4f005d564 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-attributes/dep1.js @@ -0,0 +1,3 @@ +export const Foo = () => {}; +export const value = 'value 1'; +console.log(Foo, value); diff --git a/test/form/samples/jsx/preserves-jsx-attributes/dep2.js b/test/form/samples/jsx/preserves-jsx-attributes/dep2.js new file mode 100644 index 00000000000..697b1397701 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-attributes/dep2.js @@ -0,0 +1,3 @@ +export const Foo = () => {}; +export const value = 'value 2'; +console.log(Foo, value); diff --git a/test/form/samples/jsx/preserves-jsx-attributes/dep3.js b/test/form/samples/jsx/preserves-jsx-attributes/dep3.js new file mode 100644 index 00000000000..d80368ea8cf --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-attributes/dep3.js @@ -0,0 +1,3 @@ +export const Foo = () => {}; +export const value = 'value 3'; +console.log(Foo, value); diff --git a/test/form/samples/jsx/preserves-jsx-attributes/main.js b/test/form/samples/jsx/preserves-jsx-attributes/main.js new file mode 100644 index 00000000000..2297bc014f8 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-attributes/main.js @@ -0,0 +1,11 @@ +import './dep1.js'; +import { Foo, value } from './dep2.js'; +import './dep3.js'; + +export const result = + fragment=<> +/>; diff --git a/test/form/samples/jsx/preserves-jsx-child/_config.js b/test/form/samples/jsx/preserves-jsx-child/_config.js new file mode 100644 index 00000000000..6eb31e3795c --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-child/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX children', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-child/_expected.js b/test/form/samples/jsx/preserves-jsx-child/_expected.js new file mode 100644 index 00000000000..151374d5c7b --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-child/_expected.js @@ -0,0 +1,8 @@ +const Foo$2 = 'wrong Foo 1'; +console.log(Foo$2); + +const Foo$1 = () => {}; +console.log(); + +const Foo = 'wrong Foo 2'; +console.log(Foo); diff --git a/test/form/samples/jsx/preserves-jsx-child/jsx.js b/test/form/samples/jsx/preserves-jsx-child/jsx.js new file mode 100644 index 00000000000..ed02421e9af --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-child/jsx.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/form/samples/jsx/preserves-jsx-child/main.js b/test/form/samples/jsx/preserves-jsx-child/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-child/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/preserves-jsx-child/other1.js b/test/form/samples/jsx/preserves-jsx-child/other1.js new file mode 100644 index 00000000000..297f5dfcc5b --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-child/other1.js @@ -0,0 +1,2 @@ +const Foo = 'wrong Foo 1'; +console.log(Foo); diff --git a/test/form/samples/jsx/preserves-jsx-child/other2.js b/test/form/samples/jsx/preserves-jsx-child/other2.js new file mode 100644 index 00000000000..e274d4042dd --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-child/other2.js @@ -0,0 +1,2 @@ +const Foo = 'wrong Foo 2'; +console.log(Foo); diff --git a/test/form/samples/jsx/preserves-jsx-closing/_config.js b/test/form/samples/jsx/preserves-jsx-closing/_config.js new file mode 100644 index 00000000000..b5f1855f6a3 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-closing/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX closing element', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-closing/_expected.js b/test/form/samples/jsx/preserves-jsx-closing/_expected.js new file mode 100644 index 00000000000..7b202a3cf51 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-closing/_expected.js @@ -0,0 +1,4 @@ +const Foo = () => {}; +const result = ; + +export { result }; diff --git a/test/form/samples/jsx/preserves-jsx-closing/main.js b/test/form/samples/jsx/preserves-jsx-closing/main.js new file mode 100644 index 00000000000..9c2716421ca --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-closing/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = ; diff --git a/test/form/samples/jsx/preserves-jsx-empty-expression/_config.js b/test/form/samples/jsx/preserves-jsx-empty-expression/_config.js new file mode 100644 index 00000000000..1563f600507 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-empty-expression/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX output', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-empty-expression/_expected.js b/test/form/samples/jsx/preserves-jsx-empty-expression/_expected.js new file mode 100644 index 00000000000..f450ad4b610 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-empty-expression/_expected.js @@ -0,0 +1,4 @@ +const Foo = () => {}; +const result = {/*Wohoo*/}; + +export { result }; diff --git a/test/form/samples/jsx/preserves-jsx-empty-expression/main.js b/test/form/samples/jsx/preserves-jsx-empty-expression/main.js new file mode 100644 index 00000000000..d37a6f1949a --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-empty-expression/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = {/*Wohoo*/}; diff --git a/test/form/samples/jsx/preserves-jsx-expression/_config.js b/test/form/samples/jsx/preserves-jsx-expression/_config.js new file mode 100644 index 00000000000..5d94a414623 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-expression/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX expressions', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-expression/_expected.js b/test/form/samples/jsx/preserves-jsx-expression/_expected.js new file mode 100644 index 00000000000..09dcebdd9d3 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-expression/_expected.js @@ -0,0 +1,13 @@ +const element$2 = 'element 1'; +console.log(element$2); + +const element$1 = 'element 2'; +console.log(element$1); + +const element = 'element 3'; +console.log(element); + +const Foo = () => {}; +const result = {'test' + element$1}; + +export { result }; diff --git a/test/form/samples/jsx/preserves-jsx-expression/dep1.js b/test/form/samples/jsx/preserves-jsx-expression/dep1.js new file mode 100644 index 00000000000..dd37912ccca --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-expression/dep1.js @@ -0,0 +1,2 @@ +export const element = 'element 1'; +console.log(element); diff --git a/test/form/samples/jsx/preserves-jsx-expression/dep2.js b/test/form/samples/jsx/preserves-jsx-expression/dep2.js new file mode 100644 index 00000000000..a9713f489a9 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-expression/dep2.js @@ -0,0 +1,2 @@ +export const element = 'element 2'; +console.log(element); diff --git a/test/form/samples/jsx/preserves-jsx-expression/dep3.js b/test/form/samples/jsx/preserves-jsx-expression/dep3.js new file mode 100644 index 00000000000..de220a40890 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-expression/dep3.js @@ -0,0 +1,2 @@ +export const element = 'element 3'; +console.log(element); diff --git a/test/form/samples/jsx/preserves-jsx-expression/main.js b/test/form/samples/jsx/preserves-jsx-expression/main.js new file mode 100644 index 00000000000..910b3247986 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-expression/main.js @@ -0,0 +1,6 @@ +import "./dep1.js"; +import { element } from "./dep2.js"; +import "./dep3.js"; + +const Foo = () => {}; +export const result = {'test' + element}; diff --git a/test/form/samples/jsx/preserves-jsx-fragment/_config.js b/test/form/samples/jsx/preserves-jsx-fragment/_config.js new file mode 100644 index 00000000000..1563f600507 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-fragment/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX output', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-fragment/_expected.js b/test/form/samples/jsx/preserves-jsx-fragment/_expected.js new file mode 100644 index 00000000000..594a20cbde1 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-fragment/_expected.js @@ -0,0 +1,4 @@ +const Foo = () => {}; +const result = <>; + +export { result }; diff --git a/test/form/samples/jsx/preserves-jsx-fragment/main.js b/test/form/samples/jsx/preserves-jsx-fragment/main.js new file mode 100644 index 00000000000..14a6baf4604 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-fragment/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = <>; diff --git a/test/form/samples/jsx/preserves-jsx-member-expression/_config.js b/test/form/samples/jsx/preserves-jsx-member-expression/_config.js new file mode 100644 index 00000000000..2b51fdc7286 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-member-expression/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX member expressions', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-member-expression/_expected.js b/test/form/samples/jsx/preserves-jsx-member-expression/_expected.js new file mode 100644 index 00000000000..e4db00ef475 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-member-expression/_expected.js @@ -0,0 +1,9 @@ +const obj = { + Foo: () => {}, + bar: { Baz: () => {} } +}; + +const result1 = ; +const result2 = ; + +export { result1, result2 }; diff --git a/test/form/samples/jsx/preserves-jsx-member-expression/main.js b/test/form/samples/jsx/preserves-jsx-member-expression/main.js new file mode 100644 index 00000000000..04e40c09d07 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-member-expression/main.js @@ -0,0 +1,7 @@ +const obj = { + Foo: () => {}, + bar: { Baz: () => {} } +}; + +export const result1 = ; +export const result2 = ; diff --git a/test/form/samples/jsx/preserves-jsx-self-closing/_config.js b/test/form/samples/jsx/preserves-jsx-self-closing/_config.js new file mode 100644 index 00000000000..a94325994ee --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-self-closing/_config.js @@ -0,0 +1,6 @@ +module.exports = defineTest({ + description: 'preserves self-closing JSX elements', + options: { + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-self-closing/_expected.js b/test/form/samples/jsx/preserves-jsx-self-closing/_expected.js new file mode 100644 index 00000000000..5764a03cb8e --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-self-closing/_expected.js @@ -0,0 +1,4 @@ +const Foo = () => {}; +const result = ; + +export { result }; diff --git a/test/form/samples/jsx/preserves-jsx-self-closing/main.js b/test/form/samples/jsx/preserves-jsx-self-closing/main.js new file mode 100644 index 00000000000..1e56ff70298 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-self-closing/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = ; diff --git a/test/form/samples/jsx/preserves-jsx-spread-attribute/_config.js b/test/form/samples/jsx/preserves-jsx-spread-attribute/_config.js new file mode 100644 index 00000000000..5554b472072 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-attribute/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX spread attributes', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-spread-attribute/_expected.js b/test/form/samples/jsx/preserves-jsx-spread-attribute/_expected.js new file mode 100644 index 00000000000..382aa99a5cf --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-attribute/_expected.js @@ -0,0 +1,20 @@ +const obj$2 = { value1: true }; +console.log(obj$2); + +const obj$1 = { value2: true }; +console.log(obj$1); + +const obj = { value3: true }; +console.log(obj); + +const Foo = () => {}; +const result1 = ; +const result2 = ; +const result3 = ; + +export { result1, result2, result3 }; diff --git a/test/form/samples/jsx/preserves-jsx-spread-attribute/dep1.js b/test/form/samples/jsx/preserves-jsx-spread-attribute/dep1.js new file mode 100644 index 00000000000..658d5636c5a --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-attribute/dep1.js @@ -0,0 +1,2 @@ +export const obj = { value1: true }; +console.log(obj); diff --git a/test/form/samples/jsx/preserves-jsx-spread-attribute/dep2.js b/test/form/samples/jsx/preserves-jsx-spread-attribute/dep2.js new file mode 100644 index 00000000000..bdbe905d08d --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-attribute/dep2.js @@ -0,0 +1,2 @@ +export const obj = { value2: true }; +console.log(obj); diff --git a/test/form/samples/jsx/preserves-jsx-spread-attribute/dep3.js b/test/form/samples/jsx/preserves-jsx-spread-attribute/dep3.js new file mode 100644 index 00000000000..f18273863e3 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-attribute/dep3.js @@ -0,0 +1,2 @@ +export const obj = { value3: true }; +console.log(obj); diff --git a/test/form/samples/jsx/preserves-jsx-spread-attribute/main.js b/test/form/samples/jsx/preserves-jsx-spread-attribute/main.js new file mode 100644 index 00000000000..22953c47609 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-attribute/main.js @@ -0,0 +1,13 @@ +import './dep1.js'; +import { obj } from './dep2.js'; +import './dep3.js'; + +const Foo = () => {}; +export const result1 = ; +export const result2 = ; +export const result3 = ; diff --git a/test/form/samples/jsx/preserves-jsx-spread-child/_config.js b/test/form/samples/jsx/preserves-jsx-spread-child/_config.js new file mode 100644 index 00000000000..e846a8043e6 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-child/_config.js @@ -0,0 +1,9 @@ +module.exports = defineTest({ + description: 'preserves JSX spread children', + options: { + external: ['react'], + jsx: 'preserve' + }, + // apparently, acorn-jsx does not support spread children + verifyAst: false +}); diff --git a/test/form/samples/jsx/preserves-jsx-spread-child/_expected.js b/test/form/samples/jsx/preserves-jsx-spread-child/_expected.js new file mode 100644 index 00000000000..139d1277ca3 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-child/_expected.js @@ -0,0 +1,14 @@ +const spread$2 = ['spread 1']; +console.log(spread$2); + +const spread$1 = ['spread 2']; +console.log(spread$1); + +const spread = ['spread 3']; +console.log(spread); + +const Foo = () => {}; +const element = {...spread$1}; +const fragment = <>{...spread$1}; + +export { element, fragment }; diff --git a/test/form/samples/jsx/preserves-jsx-spread-child/dep1.js b/test/form/samples/jsx/preserves-jsx-spread-child/dep1.js new file mode 100644 index 00000000000..53572b805ec --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-child/dep1.js @@ -0,0 +1,2 @@ +export const spread = ['spread 1']; +console.log(spread); diff --git a/test/form/samples/jsx/preserves-jsx-spread-child/dep2.js b/test/form/samples/jsx/preserves-jsx-spread-child/dep2.js new file mode 100644 index 00000000000..bbea244bcfb --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-child/dep2.js @@ -0,0 +1,2 @@ +export const spread = ['spread 2']; +console.log(spread); diff --git a/test/form/samples/jsx/preserves-jsx-spread-child/dep3.js b/test/form/samples/jsx/preserves-jsx-spread-child/dep3.js new file mode 100644 index 00000000000..666d48094d1 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-child/dep3.js @@ -0,0 +1,2 @@ +export const spread = ['spread 3']; +console.log(spread); diff --git a/test/form/samples/jsx/preserves-jsx-spread-child/main.js b/test/form/samples/jsx/preserves-jsx-spread-child/main.js new file mode 100644 index 00000000000..10e217564e0 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-spread-child/main.js @@ -0,0 +1,6 @@ +import './dep1.js'; +import { spread } from './dep2.js'; +import './dep3.js'; +const Foo = () => {}; +export const element = {...spread}; +export const fragment = <>{...spread}; diff --git a/test/form/samples/jsx/preserves-jsx-text/_config.js b/test/form/samples/jsx/preserves-jsx-text/_config.js new file mode 100644 index 00000000000..74acdb423be --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-text/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves JSX text', + options: { + external: ['react'], + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-jsx-text/_expected.js b/test/form/samples/jsx/preserves-jsx-text/_expected.js new file mode 100644 index 00000000000..3e62794eb38 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-text/_expected.js @@ -0,0 +1,5 @@ +const Foo = () => {}; +const element = some&\text; +const fragment = <>other&\text; + +export { element, fragment }; diff --git a/test/form/samples/jsx/preserves-jsx-text/main.js b/test/form/samples/jsx/preserves-jsx-text/main.js new file mode 100644 index 00000000000..e8e2d995543 --- /dev/null +++ b/test/form/samples/jsx/preserves-jsx-text/main.js @@ -0,0 +1,3 @@ +const Foo = () => {}; +export const element = some&\text; +export const fragment = <>other&\text; diff --git a/test/form/samples/jsx/preserves-native-elements/_config.js b/test/form/samples/jsx/preserves-native-elements/_config.js new file mode 100644 index 00000000000..d64ae8ee707 --- /dev/null +++ b/test/form/samples/jsx/preserves-native-elements/_config.js @@ -0,0 +1,6 @@ +module.exports = defineTest({ + description: 'preserves native JSX elements', + options: { + jsx: 'preserve' + } +}); diff --git a/test/form/samples/jsx/preserves-native-elements/_expected.js b/test/form/samples/jsx/preserves-native-elements/_expected.js new file mode 100644 index 00000000000..c0427dd888e --- /dev/null +++ b/test/form/samples/jsx/preserves-native-elements/_expected.js @@ -0,0 +1,10 @@ +const div$1 = 'wrong div 1'; +const span$1 = 'wrong span 1'; +console.log(div$1, span$1); + +console.log(
); +console.log(
); + +const div = 'wrong div 2'; +const span = 'wrong span 2'; +console.log(div, span); diff --git a/test/form/samples/jsx/preserves-native-elements/jsx.js b/test/form/samples/jsx/preserves-native-elements/jsx.js new file mode 100644 index 00000000000..02159671af3 --- /dev/null +++ b/test/form/samples/jsx/preserves-native-elements/jsx.js @@ -0,0 +1,5 @@ +const div = 'wrong div'; +const span = 'wrong span'; + +console.log(
); +console.log(
); diff --git a/test/form/samples/jsx/preserves-native-elements/main.js b/test/form/samples/jsx/preserves-native-elements/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/preserves-native-elements/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/preserves-native-elements/other1.js b/test/form/samples/jsx/preserves-native-elements/other1.js new file mode 100644 index 00000000000..d607c512add --- /dev/null +++ b/test/form/samples/jsx/preserves-native-elements/other1.js @@ -0,0 +1,3 @@ +const div = 'wrong div 1'; +const span = 'wrong span 1'; +console.log(div, span); diff --git a/test/form/samples/jsx/preserves-native-elements/other2.js b/test/form/samples/jsx/preserves-native-elements/other2.js new file mode 100644 index 00000000000..25f1a5e7ad7 --- /dev/null +++ b/test/form/samples/jsx/preserves-native-elements/other2.js @@ -0,0 +1,3 @@ +const div = 'wrong div 2'; +const span = 'wrong span 2'; +console.log(div, span); diff --git a/test/form/samples/jsx/preserves-react-global/_config.js b/test/form/samples/jsx/preserves-react-global/_config.js new file mode 100644 index 00000000000..c694db34942 --- /dev/null +++ b/test/form/samples/jsx/preserves-react-global/_config.js @@ -0,0 +1,10 @@ +module.exports = defineTest({ + description: 'preserves React variable when preserving JSX output', + options: { + jsx: { + factory: 'React.createElement', + fragmentFactory: 'React.Fragment', + mode: 'preserve' + } + } +}); diff --git a/test/form/samples/jsx/preserves-react-global/_expected.js b/test/form/samples/jsx/preserves-react-global/_expected.js new file mode 100644 index 00000000000..62f669e6899 --- /dev/null +++ b/test/form/samples/jsx/preserves-react-global/_expected.js @@ -0,0 +1,10 @@ +const Foo$2 = () => {}; +const React$2 = () => {}; +console.log(Foo$2, React$2); + +const Foo$1 = () => {}; +console.log(); + +const Foo = () => {}; +const React$1 = () => {}; +console.log(Foo, React$1); diff --git a/test/form/samples/jsx/preserves-react-global/jsx.js b/test/form/samples/jsx/preserves-react-global/jsx.js new file mode 100644 index 00000000000..4b5671bbbac --- /dev/null +++ b/test/form/samples/jsx/preserves-react-global/jsx.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/form/samples/jsx/preserves-react-global/main.js b/test/form/samples/jsx/preserves-react-global/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/preserves-react-global/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/preserves-react-global/other1.js b/test/form/samples/jsx/preserves-react-global/other1.js new file mode 100644 index 00000000000..dc7480c221c --- /dev/null +++ b/test/form/samples/jsx/preserves-react-global/other1.js @@ -0,0 +1,3 @@ +const Foo = () => {}; +const React = () => {}; +console.log(Foo, React); diff --git a/test/form/samples/jsx/preserves-react-global/other2.js b/test/form/samples/jsx/preserves-react-global/other2.js new file mode 100644 index 00000000000..dc7480c221c --- /dev/null +++ b/test/form/samples/jsx/preserves-react-global/other2.js @@ -0,0 +1,3 @@ +const Foo = () => {}; +const React = () => {}; +console.log(Foo, React); diff --git a/test/form/samples/jsx/preserves-react-internal/_config.js b/test/form/samples/jsx/preserves-react-internal/_config.js new file mode 100644 index 00000000000..4601d0902f0 --- /dev/null +++ b/test/form/samples/jsx/preserves-react-internal/_config.js @@ -0,0 +1,11 @@ +const path = require('node:path'); + +module.exports = defineTest({ + description: 'preserves internal React variable when preserving JSX output', + options: { + jsx: { + importSource: path.join(__dirname, 'react.js'), + preset: 'preserve-react' + } + } +}); diff --git a/test/form/samples/jsx/preserves-react-internal/_expected.js b/test/form/samples/jsx/preserves-react-internal/_expected.js new file mode 100644 index 00000000000..1d4e574bb0e --- /dev/null +++ b/test/form/samples/jsx/preserves-react-internal/_expected.js @@ -0,0 +1,16 @@ +const Foo$2 = () => {}; +const React$2 = () => {}; +console.log(Foo$2, React$2); + +var React = { + createElement() { + console.log('createElement'); + } +}; + +const Foo$1 = () => {}; +console.log(); + +const Foo = () => {}; +const React$1 = () => {}; +console.log(Foo, React$1); diff --git a/test/form/samples/jsx/preserves-react-internal/jsx.js b/test/form/samples/jsx/preserves-react-internal/jsx.js new file mode 100644 index 00000000000..4b5671bbbac --- /dev/null +++ b/test/form/samples/jsx/preserves-react-internal/jsx.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/form/samples/jsx/preserves-react-internal/main.js b/test/form/samples/jsx/preserves-react-internal/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/preserves-react-internal/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/preserves-react-internal/other1.js b/test/form/samples/jsx/preserves-react-internal/other1.js new file mode 100644 index 00000000000..dc7480c221c --- /dev/null +++ b/test/form/samples/jsx/preserves-react-internal/other1.js @@ -0,0 +1,3 @@ +const Foo = () => {}; +const React = () => {}; +console.log(Foo, React); diff --git a/test/form/samples/jsx/preserves-react-internal/other2.js b/test/form/samples/jsx/preserves-react-internal/other2.js new file mode 100644 index 00000000000..dc7480c221c --- /dev/null +++ b/test/form/samples/jsx/preserves-react-internal/other2.js @@ -0,0 +1,3 @@ +const Foo = () => {}; +const React = () => {}; +console.log(Foo, React); diff --git a/test/form/samples/jsx/preserves-react-internal/react.js b/test/form/samples/jsx/preserves-react-internal/react.js new file mode 100644 index 00000000000..62d36813017 --- /dev/null +++ b/test/form/samples/jsx/preserves-react-internal/react.js @@ -0,0 +1,5 @@ +export default { + createElement() { + console.log('createElement'); + } +}; diff --git a/test/form/samples/jsx/preserves-react/_config.js b/test/form/samples/jsx/preserves-react/_config.js new file mode 100644 index 00000000000..ca4145a5e30 --- /dev/null +++ b/test/form/samples/jsx/preserves-react/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves React variable when preserving JSX output', + options: { + external: ['react'], + jsx: 'preserve-react' + } +}); diff --git a/test/form/samples/jsx/preserves-react/_expected.js b/test/form/samples/jsx/preserves-react/_expected.js new file mode 100644 index 00000000000..62586a3c8a3 --- /dev/null +++ b/test/form/samples/jsx/preserves-react/_expected.js @@ -0,0 +1,12 @@ +import React from 'react'; + +const Foo$2 = () => {}; +const React$2 = () => {}; +console.log(Foo$2, React$2); + +const Foo$1 = () => {}; +console.log(); + +const Foo = () => {}; +const React$1 = () => {}; +console.log(Foo, React$1); diff --git a/test/form/samples/jsx/preserves-react/jsx.js b/test/form/samples/jsx/preserves-react/jsx.js new file mode 100644 index 00000000000..bb22f6edde5 --- /dev/null +++ b/test/form/samples/jsx/preserves-react/jsx.js @@ -0,0 +1,4 @@ +import React from "react"; + +const Foo = () => {}; +console.log(); diff --git a/test/form/samples/jsx/preserves-react/main.js b/test/form/samples/jsx/preserves-react/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/preserves-react/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/preserves-react/other1.js b/test/form/samples/jsx/preserves-react/other1.js new file mode 100644 index 00000000000..dc7480c221c --- /dev/null +++ b/test/form/samples/jsx/preserves-react/other1.js @@ -0,0 +1,3 @@ +const Foo = () => {}; +const React = () => {}; +console.log(Foo, React); diff --git a/test/form/samples/jsx/preserves-react/other2.js b/test/form/samples/jsx/preserves-react/other2.js new file mode 100644 index 00000000000..dc7480c221c --- /dev/null +++ b/test/form/samples/jsx/preserves-react/other2.js @@ -0,0 +1,3 @@ +const Foo = () => {}; +const React = () => {}; +console.log(Foo, React); diff --git a/test/form/samples/jsx/transpiles-automatic-with-defaults/_config.js b/test/form/samples/jsx/transpiles-automatic-with-defaults/_config.js new file mode 100644 index 00000000000..77000dc3995 --- /dev/null +++ b/test/form/samples/jsx/transpiles-automatic-with-defaults/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX for react', + options: { + external: ['react', 'react/jsx-runtime'], + jsx: { mode: 'automatic' } + } +}); diff --git a/test/form/samples/jsx/transpiles-automatic-with-defaults/_expected.js b/test/form/samples/jsx/transpiles-automatic-with-defaults/_expected.js new file mode 100644 index 00000000000..4181ccce6f9 --- /dev/null +++ b/test/form/samples/jsx/transpiles-automatic-with-defaults/_expected.js @@ -0,0 +1,11 @@ +import { jsx, Fragment, jsxs } from 'react/jsx-runtime'; +import react from 'react'; + +const Foo = () => {}; +const obj = { key: '2' }; + +console.log(/*#__PURE__*/jsx(Foo, {})); +console.log(/*#__PURE__*/jsx(Fragment, { children: /*#__PURE__*/jsx(Foo, {}) })); +console.log(/*#__PURE__*/jsxs(Foo, { children: [/*#__PURE__*/jsx(Foo, {}), /*#__PURE__*/jsx(Foo, {})] })); +console.log(/*#__PURE__*/jsxs(Fragment, { children: [/*#__PURE__*/jsx(Foo, {}), /*#__PURE__*/jsx(Foo, {})] })); +console.log(/*#__PURE__*/react.createElement(Foo, Object.assign({}, obj, { key: "1" }))); diff --git a/test/form/samples/jsx/transpiles-automatic-with-defaults/main.js b/test/form/samples/jsx/transpiles-automatic-with-defaults/main.js new file mode 100644 index 00000000000..13f357d5f77 --- /dev/null +++ b/test/form/samples/jsx/transpiles-automatic-with-defaults/main.js @@ -0,0 +1,8 @@ +const Foo = () => {}; +const obj = { key: '2' }; + +console.log(); +console.log(<>); +console.log(); +console.log(<>); +console.log(); diff --git a/test/form/samples/jsx/transpiles-classic-with-defaults/_config.js b/test/form/samples/jsx/transpiles-classic-with-defaults/_config.js new file mode 100644 index 00000000000..61589a99124 --- /dev/null +++ b/test/form/samples/jsx/transpiles-classic-with-defaults/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX for react', + options: { + external: ['react', 'react/jsx-runtime'], + jsx: { mode: 'classic' } + } +}); diff --git a/test/form/samples/jsx/transpiles-classic-with-defaults/_expected.js b/test/form/samples/jsx/transpiles-classic-with-defaults/_expected.js new file mode 100644 index 00000000000..758900d85bb --- /dev/null +++ b/test/form/samples/jsx/transpiles-classic-with-defaults/_expected.js @@ -0,0 +1,8 @@ +const Foo = () => {}; +const obj = { key: '2' }; + +console.log(/*#__PURE__*/React.createElement(Foo, null)); +console.log(/*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Foo, null))); +console.log(/*#__PURE__*/React.createElement(Foo, null, /*#__PURE__*/React.createElement(Foo, null), /*#__PURE__*/React.createElement(Foo, null))); +console.log(/*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Foo, null), /*#__PURE__*/React.createElement(Foo, null))); +console.log(/*#__PURE__*/React.createElement(Foo, Object.assign({}, obj, { key: "1" }))); diff --git a/test/form/samples/jsx/transpiles-classic-with-defaults/main.js b/test/form/samples/jsx/transpiles-classic-with-defaults/main.js new file mode 100644 index 00000000000..13f357d5f77 --- /dev/null +++ b/test/form/samples/jsx/transpiles-classic-with-defaults/main.js @@ -0,0 +1,8 @@ +const Foo = () => {}; +const obj = { key: '2' }; + +console.log(); +console.log(<>); +console.log(); +console.log(<>); +console.log(); diff --git a/test/form/samples/jsx/transpiles-empty-fragment/_config.js b/test/form/samples/jsx/transpiles-empty-fragment/_config.js new file mode 100644 index 00000000000..d9c8a8452c7 --- /dev/null +++ b/test/form/samples/jsx/transpiles-empty-fragment/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX for react', + options: { + external: ['react', 'react/jsx-runtime'], + jsx: 'react-jsx' + } +}); diff --git a/test/form/samples/jsx/transpiles-empty-fragment/_expected.js b/test/form/samples/jsx/transpiles-empty-fragment/_expected.js new file mode 100644 index 00000000000..5fd112f7f75 --- /dev/null +++ b/test/form/samples/jsx/transpiles-empty-fragment/_expected.js @@ -0,0 +1,3 @@ +import { jsx, Fragment } from 'react/jsx-runtime'; + +console.log(/*#__PURE__*/jsx(Fragment, {})); diff --git a/test/form/samples/jsx/transpiles-empty-fragment/main.js b/test/form/samples/jsx/transpiles-empty-fragment/main.js new file mode 100644 index 00000000000..f6957f2d75b --- /dev/null +++ b/test/form/samples/jsx/transpiles-empty-fragment/main.js @@ -0,0 +1 @@ +console.log(<>); diff --git a/test/form/samples/jsx/transpiles-jsx-attributes/_config.js b/test/form/samples/jsx/transpiles-jsx-attributes/_config.js new file mode 100644 index 00000000000..6eec3cae05e --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-attributes/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX with string attributes output', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-attributes/_expected.js b/test/form/samples/jsx/transpiles-jsx-attributes/_expected.js new file mode 100644 index 00000000000..f058ee679e1 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-attributes/_expected.js @@ -0,0 +1,22 @@ +import react from 'react'; + +const Foo$2 = () => {}; +const value$2 = 'value 1'; +console.log(Foo$2, value$2); + +const Foo$1 = () => {}; +const value$1 = 'value 2'; +console.log(Foo$1, value$1); + +const Foo = () => {}; +const value = 'value 3'; +console.log(Foo, value); + +const result = /*#__PURE__*/react.createElement(Foo$1, + { bar: true, + "baz:foo": "string", + "quux-nix": value$1, + element: /*#__PURE__*/react.createElement(Foo$1, null), + fragment: /*#__PURE__*/react.createElement(react.Fragment, null) }); + +export { result }; diff --git a/test/form/samples/jsx/transpiles-jsx-attributes/dep1.js b/test/form/samples/jsx/transpiles-jsx-attributes/dep1.js new file mode 100644 index 00000000000..7e4f005d564 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-attributes/dep1.js @@ -0,0 +1,3 @@ +export const Foo = () => {}; +export const value = 'value 1'; +console.log(Foo, value); diff --git a/test/form/samples/jsx/transpiles-jsx-attributes/dep2.js b/test/form/samples/jsx/transpiles-jsx-attributes/dep2.js new file mode 100644 index 00000000000..697b1397701 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-attributes/dep2.js @@ -0,0 +1,3 @@ +export const Foo = () => {}; +export const value = 'value 2'; +console.log(Foo, value); diff --git a/test/form/samples/jsx/transpiles-jsx-attributes/dep3.js b/test/form/samples/jsx/transpiles-jsx-attributes/dep3.js new file mode 100644 index 00000000000..d80368ea8cf --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-attributes/dep3.js @@ -0,0 +1,3 @@ +export const Foo = () => {}; +export const value = 'value 3'; +console.log(Foo, value); diff --git a/test/form/samples/jsx/transpiles-jsx-attributes/main.js b/test/form/samples/jsx/transpiles-jsx-attributes/main.js new file mode 100644 index 00000000000..2297bc014f8 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-attributes/main.js @@ -0,0 +1,11 @@ +import './dep1.js'; +import { Foo, value } from './dep2.js'; +import './dep3.js'; + +export const result = + fragment=<> +/>; diff --git a/test/form/samples/jsx/transpiles-jsx-child/_config.js b/test/form/samples/jsx/transpiles-jsx-child/_config.js new file mode 100644 index 00000000000..66790a15999 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-child/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX children', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-child/_expected.js b/test/form/samples/jsx/transpiles-jsx-child/_expected.js new file mode 100644 index 00000000000..4bd347ce59f --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-child/_expected.js @@ -0,0 +1,6 @@ +import react from 'react'; + +const Foo = () => {}; +const result = /*#__PURE__*/react.createElement(Foo, null, /*#__PURE__*/react.createElement(Foo, null)); + +export { result }; diff --git a/test/form/samples/jsx/transpiles-jsx-child/main.js b/test/form/samples/jsx/transpiles-jsx-child/main.js new file mode 100644 index 00000000000..c863c74f668 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-child/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = ; diff --git a/test/form/samples/jsx/transpiles-jsx-closing/_config.js b/test/form/samples/jsx/transpiles-jsx-closing/_config.js new file mode 100644 index 00000000000..719ece5beb1 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-closing/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX closing element', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-closing/_expected.js b/test/form/samples/jsx/transpiles-jsx-closing/_expected.js new file mode 100644 index 00000000000..1a54860615d --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-closing/_expected.js @@ -0,0 +1,6 @@ +import react from 'react'; + +const Foo = () => {}; +const result = /*#__PURE__*/react.createElement(Foo, null); + +export { result }; diff --git a/test/form/samples/jsx/transpiles-jsx-closing/main.js b/test/form/samples/jsx/transpiles-jsx-closing/main.js new file mode 100644 index 00000000000..9c2716421ca --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-closing/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = ; diff --git a/test/form/samples/jsx/transpiles-jsx-empty-expression/_config.js b/test/form/samples/jsx/transpiles-jsx-empty-expression/_config.js new file mode 100644 index 00000000000..154660bc4ed --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-empty-expression/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX output', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-empty-expression/_expected.js b/test/form/samples/jsx/transpiles-jsx-empty-expression/_expected.js new file mode 100644 index 00000000000..1a54860615d --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-empty-expression/_expected.js @@ -0,0 +1,6 @@ +import react from 'react'; + +const Foo = () => {}; +const result = /*#__PURE__*/react.createElement(Foo, null); + +export { result }; diff --git a/test/form/samples/jsx/transpiles-jsx-empty-expression/main.js b/test/form/samples/jsx/transpiles-jsx-empty-expression/main.js new file mode 100644 index 00000000000..d37a6f1949a --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-empty-expression/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = {/*Wohoo*/}; diff --git a/test/form/samples/jsx/transpiles-jsx-expression/_config.js b/test/form/samples/jsx/transpiles-jsx-expression/_config.js new file mode 100644 index 00000000000..217e03f576b --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-expression/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX expressions', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-expression/_expected.js b/test/form/samples/jsx/transpiles-jsx-expression/_expected.js new file mode 100644 index 00000000000..3a792d73525 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-expression/_expected.js @@ -0,0 +1,15 @@ +import react from 'react'; + +const element$2 = 'element 1'; +console.log(element$2); + +const element$1 = 'element 2'; +console.log(element$1); + +const element = 'element 3'; +console.log(element); + +const Foo = () => {}; +const result = /*#__PURE__*/react.createElement(Foo, null, 'test' + element$1); + +export { result }; diff --git a/test/form/samples/jsx/transpiles-jsx-expression/dep1.js b/test/form/samples/jsx/transpiles-jsx-expression/dep1.js new file mode 100644 index 00000000000..dd37912ccca --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-expression/dep1.js @@ -0,0 +1,2 @@ +export const element = 'element 1'; +console.log(element); diff --git a/test/form/samples/jsx/transpiles-jsx-expression/dep2.js b/test/form/samples/jsx/transpiles-jsx-expression/dep2.js new file mode 100644 index 00000000000..a9713f489a9 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-expression/dep2.js @@ -0,0 +1,2 @@ +export const element = 'element 2'; +console.log(element); diff --git a/test/form/samples/jsx/transpiles-jsx-expression/dep3.js b/test/form/samples/jsx/transpiles-jsx-expression/dep3.js new file mode 100644 index 00000000000..de220a40890 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-expression/dep3.js @@ -0,0 +1,2 @@ +export const element = 'element 3'; +console.log(element); diff --git a/test/form/samples/jsx/transpiles-jsx-expression/main.js b/test/form/samples/jsx/transpiles-jsx-expression/main.js new file mode 100644 index 00000000000..910b3247986 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-expression/main.js @@ -0,0 +1,6 @@ +import "./dep1.js"; +import { element } from "./dep2.js"; +import "./dep3.js"; + +const Foo = () => {}; +export const result = {'test' + element}; diff --git a/test/form/samples/jsx/transpiles-jsx-fragment/_config.js b/test/form/samples/jsx/transpiles-jsx-fragment/_config.js new file mode 100644 index 00000000000..154660bc4ed --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-fragment/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX output', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-fragment/_expected.js b/test/form/samples/jsx/transpiles-jsx-fragment/_expected.js new file mode 100644 index 00000000000..c9a08b770b8 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-fragment/_expected.js @@ -0,0 +1,6 @@ +import react from 'react'; + +const Foo = () => {}; +const result = /*#__PURE__*/react.createElement(Foo, null, /*#__PURE__*/react.createElement(react.Fragment, null)); + +export { result }; diff --git a/test/form/samples/jsx/transpiles-jsx-fragment/main.js b/test/form/samples/jsx/transpiles-jsx-fragment/main.js new file mode 100644 index 00000000000..14a6baf4604 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-fragment/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = <>; diff --git a/test/form/samples/jsx/transpiles-jsx-member-expression/_config.js b/test/form/samples/jsx/transpiles-jsx-member-expression/_config.js new file mode 100644 index 00000000000..dfd4b69c2a6 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-member-expression/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX member expressions', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-member-expression/_expected.js b/test/form/samples/jsx/transpiles-jsx-member-expression/_expected.js new file mode 100644 index 00000000000..7549595c533 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-member-expression/_expected.js @@ -0,0 +1,11 @@ +import react from 'react'; + +const obj = { + Foo: () => {}, + bar: { Baz: () => {} } +}; + +const result1 = /*#__PURE__*/react.createElement(obj.Foo, null); +const result2 = /*#__PURE__*/react.createElement(obj.bar.Baz, null); + +export { result1, result2 }; diff --git a/test/form/samples/jsx/transpiles-jsx-member-expression/main.js b/test/form/samples/jsx/transpiles-jsx-member-expression/main.js new file mode 100644 index 00000000000..04e40c09d07 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-member-expression/main.js @@ -0,0 +1,7 @@ +const obj = { + Foo: () => {}, + bar: { Baz: () => {} } +}; + +export const result1 = ; +export const result2 = ; diff --git a/test/form/samples/jsx/transpiles-jsx-self-closing/_config.js b/test/form/samples/jsx/transpiles-jsx-self-closing/_config.js new file mode 100644 index 00000000000..ec1eb81a545 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-self-closing/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles self-closing JSX elements', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-self-closing/_expected.js b/test/form/samples/jsx/transpiles-jsx-self-closing/_expected.js new file mode 100644 index 00000000000..1a54860615d --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-self-closing/_expected.js @@ -0,0 +1,6 @@ +import react from 'react'; + +const Foo = () => {}; +const result = /*#__PURE__*/react.createElement(Foo, null); + +export { result }; diff --git a/test/form/samples/jsx/transpiles-jsx-self-closing/main.js b/test/form/samples/jsx/transpiles-jsx-self-closing/main.js new file mode 100644 index 00000000000..1e56ff70298 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-self-closing/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +export const result = ; diff --git a/test/form/samples/jsx/transpiles-jsx-spread-attribute/_config.js b/test/form/samples/jsx/transpiles-jsx-spread-attribute/_config.js new file mode 100644 index 00000000000..edcac60d1e7 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-attribute/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX spread attributes', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-spread-attribute/_expected.js b/test/form/samples/jsx/transpiles-jsx-spread-attribute/_expected.js new file mode 100644 index 00000000000..4329ca93823 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-attribute/_expected.js @@ -0,0 +1,21 @@ +import react from 'react'; + +const obj$2 = { value1: true }; +console.log(obj$2); + +const obj$1 = { value2: true }; +console.log(obj$1); + +const obj = { value3: true }; +console.log(obj); + +const Foo = () => {}; +const result1 = /*#__PURE__*/react.createElement(Foo, obj$1); +const result2 = /*#__PURE__*/react.createElement(Foo, Object.assign({}, obj$1, { prop: true })); +const result3 = /*#__PURE__*/react.createElement(Foo, + Object.assign({ prop1: true, + prop2: true }, + obj$1, + obj$1)); + +export { result1, result2, result3 }; diff --git a/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep1.js b/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep1.js new file mode 100644 index 00000000000..658d5636c5a --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep1.js @@ -0,0 +1,2 @@ +export const obj = { value1: true }; +console.log(obj); diff --git a/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep2.js b/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep2.js new file mode 100644 index 00000000000..bdbe905d08d --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep2.js @@ -0,0 +1,2 @@ +export const obj = { value2: true }; +console.log(obj); diff --git a/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep3.js b/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep3.js new file mode 100644 index 00000000000..f18273863e3 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-attribute/dep3.js @@ -0,0 +1,2 @@ +export const obj = { value3: true }; +console.log(obj); diff --git a/test/form/samples/jsx/transpiles-jsx-spread-attribute/main.js b/test/form/samples/jsx/transpiles-jsx-spread-attribute/main.js new file mode 100644 index 00000000000..22953c47609 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-attribute/main.js @@ -0,0 +1,13 @@ +import './dep1.js'; +import { obj } from './dep2.js'; +import './dep3.js'; + +const Foo = () => {}; +export const result1 = ; +export const result2 = ; +export const result3 = ; diff --git a/test/form/samples/jsx/transpiles-jsx-spread-child/_config.js b/test/form/samples/jsx/transpiles-jsx-spread-child/_config.js new file mode 100644 index 00000000000..cc06baae2bd --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-child/_config.js @@ -0,0 +1,9 @@ +module.exports = defineTest({ + description: 'transpiles JSX spread children', + options: { + external: ['react'], + jsx: 'react' + }, + // apparently, acorn-jsx does not support spread children + verifyAst: false +}); diff --git a/test/form/samples/jsx/transpiles-jsx-spread-child/_expected.js b/test/form/samples/jsx/transpiles-jsx-spread-child/_expected.js new file mode 100644 index 00000000000..9a1f05f7ff9 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-child/_expected.js @@ -0,0 +1,16 @@ +import react from 'react'; + +const spread$2 = ['spread 1']; +console.log(spread$2); + +const spread$1 = ['spread 2']; +console.log(spread$1); + +const spread = ['spread 3']; +console.log(spread); + +const Foo = () => {}; +const element = /*#__PURE__*/react.createElement(Foo, null, ...spread$1); +const fragment = /*#__PURE__*/react.createElement(react.Fragment, null, ...spread$1); + +export { element, fragment }; diff --git a/test/form/samples/jsx/transpiles-jsx-spread-child/dep1.js b/test/form/samples/jsx/transpiles-jsx-spread-child/dep1.js new file mode 100644 index 00000000000..53572b805ec --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-child/dep1.js @@ -0,0 +1,2 @@ +export const spread = ['spread 1']; +console.log(spread); diff --git a/test/form/samples/jsx/transpiles-jsx-spread-child/dep2.js b/test/form/samples/jsx/transpiles-jsx-spread-child/dep2.js new file mode 100644 index 00000000000..bbea244bcfb --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-child/dep2.js @@ -0,0 +1,2 @@ +export const spread = ['spread 2']; +console.log(spread); diff --git a/test/form/samples/jsx/transpiles-jsx-spread-child/dep3.js b/test/form/samples/jsx/transpiles-jsx-spread-child/dep3.js new file mode 100644 index 00000000000..666d48094d1 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-child/dep3.js @@ -0,0 +1,2 @@ +export const spread = ['spread 3']; +console.log(spread); diff --git a/test/form/samples/jsx/transpiles-jsx-spread-child/main.js b/test/form/samples/jsx/transpiles-jsx-spread-child/main.js new file mode 100644 index 00000000000..10e217564e0 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-spread-child/main.js @@ -0,0 +1,6 @@ +import './dep1.js'; +import { spread } from './dep2.js'; +import './dep3.js'; +const Foo = () => {}; +export const element = {...spread}; +export const fragment = <>{...spread}; diff --git a/test/form/samples/jsx/transpiles-jsx-text/_config.js b/test/form/samples/jsx/transpiles-jsx-text/_config.js new file mode 100644 index 00000000000..d3fcf6b7956 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-text/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX text', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-jsx-text/_expected.js b/test/form/samples/jsx/transpiles-jsx-text/_expected.js new file mode 100644 index 00000000000..52f72067dad --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-text/_expected.js @@ -0,0 +1,7 @@ +import react from 'react'; + +const Foo = () => {}; +const element = /*#__PURE__*/react.createElement(Foo, null, "some&\\text"); +const fragment = /*#__PURE__*/react.createElement(react.Fragment, null, "other&\\text"); + +export { element, fragment }; diff --git a/test/form/samples/jsx/transpiles-jsx-text/main.js b/test/form/samples/jsx/transpiles-jsx-text/main.js new file mode 100644 index 00000000000..e8e2d995543 --- /dev/null +++ b/test/form/samples/jsx/transpiles-jsx-text/main.js @@ -0,0 +1,3 @@ +const Foo = () => {}; +export const element = some&\text; +export const fragment = <>other&\text; diff --git a/test/form/samples/jsx/transpiles-native-elements/_config.js b/test/form/samples/jsx/transpiles-native-elements/_config.js new file mode 100644 index 00000000000..52b1628b576 --- /dev/null +++ b/test/form/samples/jsx/transpiles-native-elements/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'preserves native JSX elements', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-native-elements/_expected.js b/test/form/samples/jsx/transpiles-native-elements/_expected.js new file mode 100644 index 00000000000..fa825355679 --- /dev/null +++ b/test/form/samples/jsx/transpiles-native-elements/_expected.js @@ -0,0 +1,12 @@ +import react from 'react'; + +const div$1 = 'wrong div 1'; +const span$1 = 'wrong span 1'; +console.log(div$1, span$1); + +console.log(/*#__PURE__*/react.createElement("div", null)); +console.log(/*#__PURE__*/react.createElement("div", null, /*#__PURE__*/react.createElement("span", null))); + +const div = 'wrong div 2'; +const span = 'wrong span 2'; +console.log(div, span); diff --git a/test/form/samples/jsx/transpiles-native-elements/jsx.js b/test/form/samples/jsx/transpiles-native-elements/jsx.js new file mode 100644 index 00000000000..02159671af3 --- /dev/null +++ b/test/form/samples/jsx/transpiles-native-elements/jsx.js @@ -0,0 +1,5 @@ +const div = 'wrong div'; +const span = 'wrong span'; + +console.log(
); +console.log(
); diff --git a/test/form/samples/jsx/transpiles-native-elements/main.js b/test/form/samples/jsx/transpiles-native-elements/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/transpiles-native-elements/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/transpiles-native-elements/other1.js b/test/form/samples/jsx/transpiles-native-elements/other1.js new file mode 100644 index 00000000000..d607c512add --- /dev/null +++ b/test/form/samples/jsx/transpiles-native-elements/other1.js @@ -0,0 +1,3 @@ +const div = 'wrong div 1'; +const span = 'wrong span 1'; +console.log(div, span); diff --git a/test/form/samples/jsx/transpiles-native-elements/other2.js b/test/form/samples/jsx/transpiles-native-elements/other2.js new file mode 100644 index 00000000000..25f1a5e7ad7 --- /dev/null +++ b/test/form/samples/jsx/transpiles-native-elements/other2.js @@ -0,0 +1,3 @@ +const div = 'wrong div 2'; +const span = 'wrong span 2'; +console.log(div, span); diff --git a/test/form/samples/jsx/transpiles-react-global/_config.js b/test/form/samples/jsx/transpiles-react-global/_config.js new file mode 100644 index 00000000000..3af41f24e69 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-global/_config.js @@ -0,0 +1,9 @@ +module.exports = defineTest({ + description: 'transpiles JSX for react', + options: { + jsx: { + factory: 'React.createElement', + fragmentFactory: 'React.Fragment' + } + } +}); diff --git a/test/form/samples/jsx/transpiles-react-global/_expected.js b/test/form/samples/jsx/transpiles-react-global/_expected.js new file mode 100644 index 00000000000..0dab4df904b --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-global/_expected.js @@ -0,0 +1,10 @@ +const Foo$2 = 'wrong Foo 1'; +const React$2 = 'wrong React 1'; +console.log(Foo$2, React$2); + +const Foo$1 = () => {}; +console.log(/*#__PURE__*/React.createElement(Foo$1, null)); + +const Foo = 'wrong Foo 2'; +const React$1 = 'wrong React 2'; +console.log(Foo, React$1); diff --git a/test/form/samples/jsx/transpiles-react-global/jsx.js b/test/form/samples/jsx/transpiles-react-global/jsx.js new file mode 100644 index 00000000000..4b5671bbbac --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-global/jsx.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/form/samples/jsx/transpiles-react-global/main.js b/test/form/samples/jsx/transpiles-react-global/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-global/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/transpiles-react-global/other1.js b/test/form/samples/jsx/transpiles-react-global/other1.js new file mode 100644 index 00000000000..7b5145191de --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-global/other1.js @@ -0,0 +1,3 @@ +const Foo = 'wrong Foo 1'; +const React = 'wrong React 1'; +console.log(Foo, React); diff --git a/test/form/samples/jsx/transpiles-react-global/other2.js b/test/form/samples/jsx/transpiles-react-global/other2.js new file mode 100644 index 00000000000..10b37745be6 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-global/other2.js @@ -0,0 +1,3 @@ +const Foo = 'wrong Foo 2'; +const React = 'wrong React 2'; +console.log(Foo, React); diff --git a/test/form/samples/jsx/transpiles-react-internal/_config.js b/test/form/samples/jsx/transpiles-react-internal/_config.js new file mode 100644 index 00000000000..4a1583e82f8 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-internal/_config.js @@ -0,0 +1,10 @@ +const path = require('node:path'); +module.exports = defineTest({ + description: 'transpiles JSX for react', + options: { + jsx: { + importSource: path.join(__dirname, 'react.js'), + preset: 'react' + } + } +}); diff --git a/test/form/samples/jsx/transpiles-react-internal/_expected.js b/test/form/samples/jsx/transpiles-react-internal/_expected.js new file mode 100644 index 00000000000..0e6ceaa7ccd --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-internal/_expected.js @@ -0,0 +1,16 @@ +const Foo$2 = 'wrong Foo 1'; +const React$1 = 'wrong React 1'; +console.log(Foo$2, React$1); + +var react = { + createElement() { + console.log('createElement'); + } +}; + +const Foo$1 = () => {}; +console.log(/*#__PURE__*/react.createElement(Foo$1, null)); + +const Foo = 'wrong Foo 2'; +const React = 'wrong React 2'; +console.log(Foo, React); diff --git a/test/form/samples/jsx/transpiles-react-internal/jsx.js b/test/form/samples/jsx/transpiles-react-internal/jsx.js new file mode 100644 index 00000000000..4b5671bbbac --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-internal/jsx.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/form/samples/jsx/transpiles-react-internal/main.js b/test/form/samples/jsx/transpiles-react-internal/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-internal/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/transpiles-react-internal/other1.js b/test/form/samples/jsx/transpiles-react-internal/other1.js new file mode 100644 index 00000000000..7b5145191de --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-internal/other1.js @@ -0,0 +1,3 @@ +const Foo = 'wrong Foo 1'; +const React = 'wrong React 1'; +console.log(Foo, React); diff --git a/test/form/samples/jsx/transpiles-react-internal/other2.js b/test/form/samples/jsx/transpiles-react-internal/other2.js new file mode 100644 index 00000000000..10b37745be6 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-internal/other2.js @@ -0,0 +1,3 @@ +const Foo = 'wrong Foo 2'; +const React = 'wrong React 2'; +console.log(Foo, React); diff --git a/test/form/samples/jsx/transpiles-react-internal/react.js b/test/form/samples/jsx/transpiles-react-internal/react.js new file mode 100644 index 00000000000..62d36813017 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-internal/react.js @@ -0,0 +1,5 @@ +export default { + createElement() { + console.log('createElement'); + } +}; diff --git a/test/form/samples/jsx/transpiles-react-jsx/_config.js b/test/form/samples/jsx/transpiles-react-jsx/_config.js new file mode 100644 index 00000000000..d9c8a8452c7 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-jsx/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX for react', + options: { + external: ['react', 'react/jsx-runtime'], + jsx: 'react-jsx' + } +}); diff --git a/test/form/samples/jsx/transpiles-react-jsx/_expected.js b/test/form/samples/jsx/transpiles-react-jsx/_expected.js new file mode 100644 index 00000000000..53b7d3dba63 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-jsx/_expected.js @@ -0,0 +1,78 @@ +import { jsx as jsx$2, Fragment, jsxs as jsxs$2 } from 'react/jsx-runtime'; +import react from 'react'; + +const Foo$2 = 'wrong Foo 1'; +const obj$2 = 'wrong obj 1'; +const jsx$1 = 'wrong jsx 1'; +const jsxs$1 = 'wrong jsxs 1'; +console.log(Foo$2, obj$2, jsx$1, jsxs$1); + +const Foo$1 = () => {}; +const obj$1 = { key: '2' }; + +// jsx +console.log(/*#__PURE__*/jsx$2(Foo$1, {})); +console.log(/*#__PURE__*/jsx$2(Foo$1, { x: true })); +console.log(/*#__PURE__*/jsx$2(Foo$1, { x: "1" })); +console.log(/*#__PURE__*/jsx$2(Foo$1, { x: "1" })); +console.log(/*#__PURE__*/jsx$2(Foo$1, {}, true)); +console.log(/*#__PURE__*/jsx$2(Foo$1, {}, "1")); +console.log(/*#__PURE__*/jsx$2(Foo$1, {}, "1")); +console.log(/*#__PURE__*/jsx$2(Foo$1, obj$1)); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({}, obj$1, { x: "1" }))); +console.log(/*#__PURE__*/jsx$2(Foo$1, obj$1, "1")); +console.log(/*#__PURE__*/jsx$2(Foo$1, obj$1, true)); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1))); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1), "1")); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1), true)); + +console.log(/*#__PURE__*/jsx$2(Foo$1, {})); +console.log(/*#__PURE__*/jsx$2(Foo$1, { x: "1" })); +console.log(/*#__PURE__*/jsx$2(Foo$1, {}, "1")); +console.log(/*#__PURE__*/jsx$2(Foo$1, {}, true)); + +console.log(/*#__PURE__*/jsx$2(Foo$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) })); +console.log(/*#__PURE__*/jsx$2(Foo$1, { x: "1", children: /*#__PURE__*/jsx$2(Foo$1, {}) })); +console.log(/*#__PURE__*/jsx$2(Foo$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) }, "1")); +console.log(/*#__PURE__*/jsx$2(Foo$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) }, true)); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({}, obj$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) }))); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({}, obj$1, { x: "1", children: /*#__PURE__*/jsx$2(Foo$1, {}) }))); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({}, obj$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) }), "1")); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({}, obj$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) }), true)); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) }))); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) }), "1")); +console.log(/*#__PURE__*/jsx$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) }), true)); + +console.log(/*#__PURE__*/jsx$2(Foo$1, { children: /*#__PURE__*/jsx$2(Foo$1, {}) })); + +console.log(/*#__PURE__*/jsx$2(Fragment, {})); +console.log(/*#__PURE__*/jsx$2(Fragment, { children: /*#__PURE__*/jsx$2(Foo$1, {}) })); + +// jsxs +console.log(/*#__PURE__*/jsxs$2(Foo$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] })); +console.log(/*#__PURE__*/jsxs$2(Foo$1, { x: "1", children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] })); +console.log(/*#__PURE__*/jsxs$2(Foo$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }, "1")); +console.log(/*#__PURE__*/jsxs$2(Foo$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }, true)); +console.log(/*#__PURE__*/jsxs$2(Foo$1, Object.assign({}, obj$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }))); +console.log(/*#__PURE__*/jsxs$2(Foo$1, Object.assign({}, obj$1, { x: "1", children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }))); +console.log(/*#__PURE__*/jsxs$2(Foo$1, Object.assign({}, obj$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }), "1")); +console.log(/*#__PURE__*/jsxs$2(Foo$1, Object.assign({}, obj$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }), true)); +console.log(/*#__PURE__*/jsxs$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }))); +console.log(/*#__PURE__*/jsxs$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }), "1")); +console.log(/*#__PURE__*/jsxs$2(Foo$1, Object.assign({ x: "1", y: "1" }, obj$1, obj$1, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] }), true)); + +console.log(/*#__PURE__*/jsxs$2(Fragment, { children: [/*#__PURE__*/jsx$2(Foo$1, {}), /*#__PURE__*/jsx$2(Foo$1, {})] })); + +// createElement +console.log(/*#__PURE__*/react.createElement(Foo$1, Object.assign({}, obj$1, { key: "1" }))); +console.log(/*#__PURE__*/react.createElement(Foo$1, Object.assign({}, obj$1, { key: true }))); +console.log(/*#__PURE__*/react.createElement(Foo$1, Object.assign({}, obj$1, obj$1, { x: "1", key: "1", y: "1" }))); +console.log(/*#__PURE__*/react.createElement(Foo$1, Object.assign({}, obj$1, obj$1, { x: "1", key: true, y: "1" }))); +console.log(/*#__PURE__*/react.createElement(Foo$1, Object.assign({}, obj$1, { key: "1" }))); +console.log(/*#__PURE__*/react.createElement(Foo$1, Object.assign({}, obj$1, { key: "1" }), /*#__PURE__*/jsx$2(Foo$1, {}))); + +const Foo = 'wrong Foo 2'; +const obj = 'wrong obj 2'; +const jsx = 'wrong jsx 2'; +const jsxs = 'wrong jsxs 2'; +console.log(Foo, obj, jsx, jsxs); diff --git a/test/form/samples/jsx/transpiles-react-jsx/jsx.js b/test/form/samples/jsx/transpiles-react-jsx/jsx.js new file mode 100644 index 00000000000..e3346bf6393 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-jsx/jsx.js @@ -0,0 +1,63 @@ +const Foo = () => {}; +const obj = { key: '2' }; + +// jsx +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); + +console.log(); +console.log(); +console.log(); +console.log(); + +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); + +console.log({/* comment */}{/* comment */}); + +console.log(<>); +console.log(<>); + +// jsxs +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); + +console.log(<>); + +// createElement +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); +console.log(); diff --git a/test/form/samples/jsx/transpiles-react-jsx/main.js b/test/form/samples/jsx/transpiles-react-jsx/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-jsx/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/transpiles-react-jsx/other1.js b/test/form/samples/jsx/transpiles-react-jsx/other1.js new file mode 100644 index 00000000000..9514c12c816 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-jsx/other1.js @@ -0,0 +1,5 @@ +const Foo = 'wrong Foo 1'; +const obj = 'wrong obj 1'; +const jsx = 'wrong jsx 1'; +const jsxs = 'wrong jsxs 1'; +console.log(Foo, obj, jsx, jsxs); diff --git a/test/form/samples/jsx/transpiles-react-jsx/other2.js b/test/form/samples/jsx/transpiles-react-jsx/other2.js new file mode 100644 index 00000000000..802e2216e58 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react-jsx/other2.js @@ -0,0 +1,5 @@ +const Foo = 'wrong Foo 2'; +const obj = 'wrong obj 2'; +const jsx = 'wrong jsx 2'; +const jsxs = 'wrong jsxs 2'; +console.log(Foo, obj, jsx, jsxs); diff --git a/test/form/samples/jsx/transpiles-react/_config.js b/test/form/samples/jsx/transpiles-react/_config.js new file mode 100644 index 00000000000..4b546d0fdcd --- /dev/null +++ b/test/form/samples/jsx/transpiles-react/_config.js @@ -0,0 +1,7 @@ +module.exports = defineTest({ + description: 'transpiles JSX for react', + options: { + external: ['react'], + jsx: 'react' + } +}); diff --git a/test/form/samples/jsx/transpiles-react/_expected.js b/test/form/samples/jsx/transpiles-react/_expected.js new file mode 100644 index 00000000000..c588a702040 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react/_expected.js @@ -0,0 +1,12 @@ +import react from 'react'; + +const Foo$2 = 'wrong Foo 1'; +const React$1 = 'wrong React 1'; +console.log(Foo$2, React$1); + +const Foo$1 = () => {}; +console.log(/*#__PURE__*/react.createElement(Foo$1, null)); + +const Foo = 'wrong Foo 2'; +const React = 'wrong React 2'; +console.log(Foo, React); diff --git a/test/form/samples/jsx/transpiles-react/jsx.js b/test/form/samples/jsx/transpiles-react/jsx.js new file mode 100644 index 00000000000..4b5671bbbac --- /dev/null +++ b/test/form/samples/jsx/transpiles-react/jsx.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/form/samples/jsx/transpiles-react/main.js b/test/form/samples/jsx/transpiles-react/main.js new file mode 100644 index 00000000000..dfccae85926 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react/main.js @@ -0,0 +1,3 @@ +import "./other1.js"; +import "./jsx.js"; +import "./other2.js"; diff --git a/test/form/samples/jsx/transpiles-react/other1.js b/test/form/samples/jsx/transpiles-react/other1.js new file mode 100644 index 00000000000..7b5145191de --- /dev/null +++ b/test/form/samples/jsx/transpiles-react/other1.js @@ -0,0 +1,3 @@ +const Foo = 'wrong Foo 1'; +const React = 'wrong React 1'; +console.log(Foo, React); diff --git a/test/form/samples/jsx/transpiles-react/other2.js b/test/form/samples/jsx/transpiles-react/other2.js new file mode 100644 index 00000000000..10b37745be6 --- /dev/null +++ b/test/form/samples/jsx/transpiles-react/other2.js @@ -0,0 +1,3 @@ +const Foo = 'wrong Foo 2'; +const React = 'wrong React 2'; +console.log(Foo, React); diff --git a/test/function/samples/jsx/missing-jsx-export/_config.js b/test/function/samples/jsx/missing-jsx-export/_config.js new file mode 100644 index 00000000000..5c33d183a3a --- /dev/null +++ b/test/function/samples/jsx/missing-jsx-export/_config.js @@ -0,0 +1,34 @@ +const path = require('node:path'); + +const ID_REACT_JSX = path.join(__dirname, 'react-jsx.js'); +const ID_MAIN = path.join(__dirname, 'main.js'); + +module.exports = defineTest({ + description: 'throws when the JSX factory is not exported', + options: { + jsx: { + jsxImportSource: ID_REACT_JSX, + preset: 'react-jsx' + } + }, + error: { + code: 'MISSING_JSX_EXPORT', + exporter: ID_REACT_JSX, + frame: ` + 1: const Foo = () => {}; + 2: console.log(); + ^`, + id: ID_MAIN, + loc: { + column: 12, + file: ID_MAIN, + line: 2 + }, + message: + 'main.js (2:12): Export "jsx" is not defined in module "react-jsx.js" even though it is needed in "main.js" to provide JSX syntax. Please check your "jsx" option.', + names: ['jsx'], + pos: 34, + url: 'https://rollupjs.org/configuration-options/#jsx', + watchFiles: [ID_MAIN, ID_REACT_JSX] + } +}); diff --git a/test/function/samples/jsx/missing-jsx-export/_expected.js b/test/function/samples/jsx/missing-jsx-export/_expected.js new file mode 100644 index 00000000000..0e6ceaa7ccd --- /dev/null +++ b/test/function/samples/jsx/missing-jsx-export/_expected.js @@ -0,0 +1,16 @@ +const Foo$2 = 'wrong Foo 1'; +const React$1 = 'wrong React 1'; +console.log(Foo$2, React$1); + +var react = { + createElement() { + console.log('createElement'); + } +}; + +const Foo$1 = () => {}; +console.log(/*#__PURE__*/react.createElement(Foo$1, null)); + +const Foo = 'wrong Foo 2'; +const React = 'wrong React 2'; +console.log(Foo, React); diff --git a/test/function/samples/jsx/missing-jsx-export/main.js b/test/function/samples/jsx/missing-jsx-export/main.js new file mode 100644 index 00000000000..4b5671bbbac --- /dev/null +++ b/test/function/samples/jsx/missing-jsx-export/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/function/samples/jsx/missing-jsx-export/react-jsx.js b/test/function/samples/jsx/missing-jsx-export/react-jsx.js new file mode 100644 index 00000000000..0245cde6d1a --- /dev/null +++ b/test/function/samples/jsx/missing-jsx-export/react-jsx.js @@ -0,0 +1 @@ +export default 'actually we need jsx'; diff --git a/test/function/samples/jsx/unknown-mode/_config.js b/test/function/samples/jsx/unknown-mode/_config.js new file mode 100644 index 00000000000..c143e864cb7 --- /dev/null +++ b/test/function/samples/jsx/unknown-mode/_config.js @@ -0,0 +1,14 @@ +module.exports = defineTest({ + description: 'throws when using an unknown jsx mode', + options: { + jsx: { + mode: 'does-not-exist' + } + }, + error: { + code: 'INVALID_OPTION', + message: + 'Invalid value "does-not-exist" for option "jsx.mode" - mode must be "automatic", "classic" or "preserve".', + url: 'https://rollupjs.org/configuration-options/#jsx' + } +}); diff --git a/test/function/samples/jsx/unknown-mode/_expected.js b/test/function/samples/jsx/unknown-mode/_expected.js new file mode 100644 index 00000000000..0e6ceaa7ccd --- /dev/null +++ b/test/function/samples/jsx/unknown-mode/_expected.js @@ -0,0 +1,16 @@ +const Foo$2 = 'wrong Foo 1'; +const React$1 = 'wrong React 1'; +console.log(Foo$2, React$1); + +var react = { + createElement() { + console.log('createElement'); + } +}; + +const Foo$1 = () => {}; +console.log(/*#__PURE__*/react.createElement(Foo$1, null)); + +const Foo = 'wrong Foo 2'; +const React = 'wrong React 2'; +console.log(Foo, React); diff --git a/test/function/samples/jsx/unknown-mode/jsx.js b/test/function/samples/jsx/unknown-mode/jsx.js new file mode 100644 index 00000000000..e4c7f512867 --- /dev/null +++ b/test/function/samples/jsx/unknown-mode/jsx.js @@ -0,0 +1 @@ +export default 'unused'; diff --git a/test/function/samples/jsx/unknown-mode/main.js b/test/function/samples/jsx/unknown-mode/main.js new file mode 100644 index 00000000000..4b5671bbbac --- /dev/null +++ b/test/function/samples/jsx/unknown-mode/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/function/samples/jsx/unnecessary-import-source/_config.js b/test/function/samples/jsx/unnecessary-import-source/_config.js new file mode 100644 index 00000000000..b6019d29303 --- /dev/null +++ b/test/function/samples/jsx/unnecessary-import-source/_config.js @@ -0,0 +1,19 @@ +const path = require('node:path'); + +const ID_JSX = path.join(__dirname, 'jsx.js'); + +module.exports = defineTest({ + description: 'throws when preserving JSX syntax with an unnecessary import source', + options: { + jsx: { + importSource: ID_JSX, + mode: 'preserve' + } + }, + error: { + code: 'INVALID_OPTION', + message: + 'Invalid value for option "jsx" - when preserving JSX and specifying an importSource, you also need to specify a factory or fragment.', + url: 'https://rollupjs.org/configuration-options/#jsx' + } +}); diff --git a/test/function/samples/jsx/unnecessary-import-source/_expected.js b/test/function/samples/jsx/unnecessary-import-source/_expected.js new file mode 100644 index 00000000000..0e6ceaa7ccd --- /dev/null +++ b/test/function/samples/jsx/unnecessary-import-source/_expected.js @@ -0,0 +1,16 @@ +const Foo$2 = 'wrong Foo 1'; +const React$1 = 'wrong React 1'; +console.log(Foo$2, React$1); + +var react = { + createElement() { + console.log('createElement'); + } +}; + +const Foo$1 = () => {}; +console.log(/*#__PURE__*/react.createElement(Foo$1, null)); + +const Foo = 'wrong Foo 2'; +const React = 'wrong React 2'; +console.log(Foo, React); diff --git a/test/function/samples/jsx/unnecessary-import-source/jsx.js b/test/function/samples/jsx/unnecessary-import-source/jsx.js new file mode 100644 index 00000000000..e4c7f512867 --- /dev/null +++ b/test/function/samples/jsx/unnecessary-import-source/jsx.js @@ -0,0 +1 @@ +export default 'unused'; diff --git a/test/function/samples/jsx/unnecessary-import-source/main.js b/test/function/samples/jsx/unnecessary-import-source/main.js new file mode 100644 index 00000000000..4b5671bbbac --- /dev/null +++ b/test/function/samples/jsx/unnecessary-import-source/main.js @@ -0,0 +1,2 @@ +const Foo = () => {}; +console.log(); diff --git a/test/function/samples/options-hook/_config.js b/test/function/samples/options-hook/_config.js index 72badede7dd..ef6bdc0398e 100644 --- a/test/function/samples/options-hook/_config.js +++ b/test/function/samples/options-hook/_config.js @@ -15,6 +15,7 @@ module.exports = defineTest({ experimentalCacheExpiry: 10, experimentalLogSideEffects: false, input: ['used'], + jsx: false, logLevel: 'info', makeAbsoluteExternalsRelative: 'ifRelativeSource', maxParallelFileOps: 20, diff --git a/test/misc/optionList.js b/test/misc/optionList.js index 7ba1fee5767..72caf132d10 100644 --- a/test/misc/optionList.js +++ b/test/misc/optionList.js @@ -1,6 +1,6 @@ exports.input = - 'cache, context, experimentalCacheExpiry, experimentalLogSideEffects, external, input, logLevel, makeAbsoluteExternalsRelative, maxParallelFileOps, moduleContext, onLog, onwarn, perf, plugins, preserveEntrySignatures, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch'; + 'cache, context, experimentalCacheExpiry, experimentalLogSideEffects, external, input, jsx, logLevel, makeAbsoluteExternalsRelative, maxParallelFileOps, moduleContext, onLog, onwarn, perf, plugins, preserveEntrySignatures, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch'; exports.flags = - 'amd, assetFileNames, banner, bundleConfigAsCjs, c, cache, chunkFileNames, compact, config, configPlugin, context, d, dir, dynamicImportInCjs, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalLogSideEffects, experimentalMinChunkSize, exports, extend, external, externalImportAssertions, externalImportAttributes, externalLiveBindings, f, failAfterWarnings, file, filterLogs, footer, forceExit, format, freeze, g, generatedCode, globals, h, hashCharacters, hoistTransitiveImports, i, importAttributesKey, indent, inlineDynamicImports, input, interop, intro, logLevel, m, makeAbsoluteExternalsRelative, manualChunks, maxParallelFileOps, minifyInternalExports, moduleContext, n, name, noConflict, o, onLog, onwarn, outro, p, paths, perf, plugin, plugins, preserveEntrySignatures, preserveModules, preserveModulesRoot, preserveSymlinks, reexportProtoFromExternal, sanitizeFileName, shimMissingExports, silent, sourcemap, sourcemapBaseUrl, sourcemapExcludeSources, sourcemapFile, sourcemapFileNames, stdin, strict, strictDeprecations, systemNullSetters, treeshake, v, validate, virtualDirname, w, waitForBundleInput, watch'; + 'amd, assetFileNames, banner, bundleConfigAsCjs, c, cache, chunkFileNames, compact, config, configPlugin, context, d, dir, dynamicImportInCjs, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalLogSideEffects, experimentalMinChunkSize, exports, extend, external, externalImportAssertions, externalImportAttributes, externalLiveBindings, f, failAfterWarnings, file, filterLogs, footer, forceExit, format, freeze, g, generatedCode, globals, h, hashCharacters, hoistTransitiveImports, i, importAttributesKey, indent, inlineDynamicImports, input, interop, intro, jsx, logLevel, m, makeAbsoluteExternalsRelative, manualChunks, maxParallelFileOps, minifyInternalExports, moduleContext, n, name, noConflict, o, onLog, onwarn, outro, p, paths, perf, plugin, plugins, preserveEntrySignatures, preserveModules, preserveModulesRoot, preserveSymlinks, reexportProtoFromExternal, sanitizeFileName, shimMissingExports, silent, sourcemap, sourcemapBaseUrl, sourcemapExcludeSources, sourcemapFile, sourcemapFileNames, stdin, strict, strictDeprecations, systemNullSetters, treeshake, v, validate, virtualDirname, w, waitForBundleInput, watch'; exports.output = 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportInCjs, entryFileNames, esModule, experimentalMinChunkSize, exports, extend, externalImportAssertions, externalImportAttributes, externalLiveBindings, file, footer, format, freeze, generatedCode, globals, hashCharacters, hoistTransitiveImports, importAttributesKey, indent, inlineDynamicImports, interop, intro, manualChunks, minifyInternalExports, name, noConflict, outro, paths, plugins, preserveModules, preserveModulesRoot, reexportProtoFromExternal, sanitizeFileName, sourcemap, sourcemapBaseUrl, sourcemapExcludeSources, sourcemapFile, sourcemapFileNames, sourcemapIgnoreList, sourcemapPathTransform, strict, systemNullSetters, validate, virtualDirname'; diff --git a/test/utils.js b/test/utils.js index 43f1dcb7592..85b09394fbe 100644 --- a/test/utils.js +++ b/test/utils.js @@ -23,6 +23,7 @@ const path = require('node:path'); const { platform, version } = require('node:process'); const { Parser } = require('acorn'); const { importAssertions } = require('acorn-import-assertions'); +const jsx = require('acorn-jsx'); const fixturify = require('fixturify'); if (!globalThis.defineTest) { @@ -454,7 +455,7 @@ exports.replaceDirectoryInStringifiedObject = function replaceDirectoryInStringi /** @type {boolean} */ exports.hasEsBuild = existsSync(path.join(__dirname, '../dist/es')); -const acornParser = Parser.extend(importAssertions); +const acornParser = Parser.extend(importAssertions, jsx()); exports.verifyAstPlugin = { name: 'verify-ast', @@ -489,6 +490,11 @@ const replaceStringifyValues = (key, value) => { const { decorators, ...rest } = value; return rest; } + case 'JSXText': { + // raw text is encoded differently in acorn + const { raw, ...nonRawProperties } = value; + return nonRawProperties; + } } return key.startsWith('_') diff --git a/wasm/bindings_wasm.d.ts b/wasm/bindings_wasm.d.ts index 8bd3e23c94b..2c00d9fee76 100644 --- a/wasm/bindings_wasm.d.ts +++ b/wasm/bindings_wasm.d.ts @@ -3,9 +3,10 @@ /** * @param {string} code * @param {boolean} allow_return_outside_function +* @param {boolean} jsx * @returns {Uint8Array} */ -export function parse(code: string, allow_return_outside_function: boolean): Uint8Array; +export function parse(code: string, allow_return_outside_function: boolean, jsx: boolean): Uint8Array; /** * @param {Uint8Array} input * @returns {string} @@ -26,7 +27,7 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl export interface InitOutput { readonly memory: WebAssembly.Memory; - readonly parse: (a: number, b: number, c: number, d: number) => void; + readonly parse: (a: number, b: number, c: number, d: number, e: number) => void; readonly xxhashBase64Url: (a: number, b: number) => void; readonly xxhashBase36: (a: number, b: number) => void; readonly xxhashBase16: (a: number, b: number) => void; diff --git a/wasm/bindings_wasm_bg.wasm.d.ts b/wasm/bindings_wasm_bg.wasm.d.ts index d9dad8cd64d..7e56b53ff93 100644 --- a/wasm/bindings_wasm_bg.wasm.d.ts +++ b/wasm/bindings_wasm_bg.wasm.d.ts @@ -1,7 +1,7 @@ /* tslint:disable */ /* eslint-disable */ export const memory: WebAssembly.Memory; -export function parse(a: number, b: number, c: number, d: number): void; +export function parse(a: number, b: number, c: number, d: number, e: number): void; export function xxhashBase64Url(a: number, b: number): void; export function xxhashBase36(a: number, b: number): void; export function xxhashBase16(a: number, b: number): void;