diff --git a/.changeset/babel-source-names-locations.md b/.changeset/babel-source-names-locations.md new file mode 100644 index 0000000000..645c92f799 --- /dev/null +++ b/.changeset/babel-source-names-locations.md @@ -0,0 +1,5 @@ +--- +"@solidjs/babel-plugin": patch +--- + +`sourceNames` gains a `locations` kind (and the plugin a `root` option): component and binding labels carry a `file:line:col` for diagnostics. `createComponent(Home, props, "Home", "src/App.tsx:12:5")` points at the ``). diff --git a/.changeset/diagnostics-source-locations-solid.md b/.changeset/diagnostics-source-locations-solid.md new file mode 100644 index 0000000000..09855d54a1 --- /dev/null +++ b/.changeset/diagnostics-source-locations-solid.md @@ -0,0 +1,5 @@ +--- +"solid-js": patch +--- + +`createComponent(Comp, props, name?, loc?)` takes the JSX site the compiler emits beside the tag (`"src/App.tsx:12:5"`); the dev and observe component owner carries it on both the client and the server, so a finding raised inside a component reports `at src/App.tsx:12:5` (and `event.loc`) when nothing closer was located. The console footer is now two lines: the code's docs page for a human (`what it means and how to fix it: https://v2.solidjs.com/reference/solid-js/diagnostics/`), then the repair skill for an agent (installed path and GitHub URL, anchored to the code's section). `diagnostics.json` links each code to its docs page, and the `reactivity-diagnostics` skill documents the `at` line. diff --git a/.changeset/diagnostics-source-locations-web.md b/.changeset/diagnostics-source-locations-web.md new file mode 100644 index 0000000000..9a78086d41 --- /dev/null +++ b/.changeset/diagnostics-source-locations-web.md @@ -0,0 +1,5 @@ +--- +"@solidjs/web": patch +--- + +`effect` and `insert` take `loc` beside `name` in their options, and `spread` a trailing `loc` after its tag — the source site the compiler emits under `sourceNames` `locations` — so a diagnostic about a compiled binding reports `at src/App.tsx:14:7` and carries `event.loc`. Dev and observe builds keep it on the node; production ignores it. diff --git a/documentation/solid-2.0/08-dev-diagnostics.md b/documentation/solid-2.0/08-dev-diagnostics.md index 5d1ecffe21..d109065bc3 100644 --- a/documentation/solid-2.0/08-dev-diagnostics.md +++ b/documentation/solid-2.0/08-dev-diagnostics.md @@ -14,8 +14,9 @@ Every console report is one entry built for a human to act on: - The message, with the code in brackets and the repair in the text. - An `in` line naming the owners enclosing the subject, root first — component roots as ``, computations by their `name` option or the `effect`/`computed` default (`in › › › effect`) — a compiled binding effect reads as what it writes (`span.textContent`, `div.class:active`, a hole `div.children`) when the JSX compiler's `sourceNames.bindings` is on (both `@solidjs/babel-plugin` and `@solidjs/compiler` take the same `sourceNames` option, on by default in dev builds alongside `components`); a primitive reads as the identifier it was declared as (`count`, `doubled`, `todos.title`, `createCounter.value` inside a composed primitive) when `@solidjs/compiler`'s standalone `transformSourceNames` pass has run — primitive naming is not a JSX-transform feature: the build tool applies that pass to every module, `.ts`/`.js` and JSX alike, independently of which JSX compiler handles the file (the Vite plugin's `sourceNames.primitives`, on by default in its dev and `observe` postures; solid-vite-plugin #371) — or as its `name` option otherwise. The same chain is `event.ownerPath` on the structured event. A component's name is the tag as written in source when the JSX compiler's `sourceNames.components` is on (`createComponent(Home, props, "Home")` — on by default in dev builds; the Vite plugin also enables it for the `observe` posture, so minified observe builds still read ``), otherwise the function's `.name`, which a minifier rewrites and a `lazy()` wrapper hides. Components a library invokes by value rather than by tag (a router rendering a route's `component`) carry only the function name. +- An `at` line when the build wrote source locations (the compiler's `sourceNames` `locations` kind — on by default in dev builds, an opt-in for `observe` ones): the innermost located node's `file:line:col`, relative to the project root — the binding, memo, effect, signal or store the finding is about (`loc` in its options literal), else the JSX site of the nearest enclosing component (`createComponent(Home, props, "Home", "src/App.tsx:12:5")`). In a browser it prints as the dev server's URL (`at http://localhost:5173/src/App.tsx:18:3`), which Chrome and Firefox link to the Sources panel; on the server, the bare path. The same string is `event.loc` on the structured event. Absent when nothing on the chain was located. - For a compiled JSX binding effect (attribute, class, style, property, spread, insert), the element it writes as a second console argument — hover highlights it on the page, click jumps to it in the Elements panel. The web runtime tags binding effects with their element in dev; the core prints whatever the subject knows. -- The first report of each code ends with a footer `solid-js` registers through an internal seam of the engine (not a public `DEV` method): the installed repair skill path (`node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md`) and the same file's stable GitHub URL anchored to the code's section — `DEV.guideUrl(code)`, the one place that URL is built, so a profiler track's Insights link (in dev) and the console footer agree. Perf, graph, and responsiveness codes add a second line pointing at `attribution.enable()` from `solid-js/attribution` and the `agent-loops` skill in `@solidjs/diagnostics`. +- The first report of each code ends with a footer `solid-js` registers through an internal seam of the engine (not a public `DEV` method): a line for a human — the code's page on the docs site, what it means and how to fix it (`DEV.guideUrl(code)`, the one place that URL is built, so a profiler track's Insights link (in dev) and the console footer agree) — and a line for an agent, the installed repair skill path (`node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md`) and the same file's stable GitHub URL, anchored to the code's section. Perf, graph, and responsiveness codes add a second line pointing at `attribution.enable()` from `solid-js/attribution` and the `agent-loops` skill in `@solidjs/diagnostics`. Attribution's own output (`[why-run]` chains) prints as collapsed console groups — one headline per re-run, the cause chain and dependency delta inside. @@ -764,7 +765,7 @@ const events = capture.stop(); ### `DEV.guideUrl(code)` -Dev builds only. The repair guide's section for a code — the `reactivity-diagnostics` skill's stable GitHub URL, anchored to the code (`…/SKILL.md#strict_read_untracked`). One place builds it: the console footer's "learn more" line and the profiler track's Insights link (`learnMoreUrl`, in dev) both read it, so a tool that renders findings elsewhere links to the same text. Dev-tier rather than observe because it is guidance for a developer, and the URL string on a retained object would be a cost every observe build paid. +Dev builds only. The code's page on the docs site — `https://v2.solidjs.com/reference/solid-js/diagnostics/`, the slug being the code lowercased with `-` for `_` (`…/diagnostics/strict-read-untracked`). One place builds it: the console footer's first line and the profiler track's Insights link (`learnMoreUrl`, in dev) both read it, so a tool that renders findings elsewhere links to the same page. Dev-tier rather than observe because it is guidance for a developer, and the URL string on a retained object would be a cost every observe build paid. ### `OBSERVE.ownerPath(subject)` diff --git a/packages/babel-plugin/README.md b/packages/babel-plugin/README.md index 78229355ed..8578c79a00 100644 --- a/packages/babel-plugin/README.md +++ b/packages/babel-plugin/README.md @@ -129,16 +129,24 @@ Development output. With `hydratable`, emits the hydration walk validation helpe ### sourceNames -- Type: `boolean | { components?: boolean; bindings?: boolean }` +- Type: `boolean | { components?: boolean; bindings?: boolean; locations?: boolean }` - Default: follows `dev` — every kind on when `dev: true`, off otherwise Names as written in source, carried into the output so the dev and observe runtimes can label the reactive graph — in diagnostic `ownerPath`s, attribution chains, and the Performance panel tracks — even after a minifier renames everything. Unset, the option follows `dev`; `true`/`false` sets every kind (`sourceNames: false` opts a dev build out); the object form picks, and each kind it leaves unspecified follows `dev`. The production runtimes ignore the names, and production output (`dev: false`) is byte-identical whether the option is set or not. `@solidjs/compiler`'s `transform()` takes the same option with the same shape and defaults. - `components`: emit the tag as written as a third `createComponent` argument — `` compiles to `createComponent(Home, props, "Home")`, `` to `"Ui.Button"` — so each component's owner reads `` even when a `lazy()`/HMR wrapper hides the function. Applies to DOM and SSR output; for SSR the compiler keeps the `createComponent` call it otherwise inlines to `Comp(props)`, so the server runtime labels the owner the same way (prod SSR output, without the option, is unchanged). Universal and dynamic output are unaffected. - `bindings`: every compiled binding effect is named by what it writes, as a trailing options argument the dev and observe runtimes read and production ignores. An attribute effect gets `.` as written — `` compiles to `effect(() => label(), v => …, { name: "span.textContent" })`, `class:active` to `div.class:active`, a `style={{ color: c() }}` property to `div.style:color` — and a template's merged effect lists all of its bindings (`"button.class, span.textContent"`). A hole's insert is named for the parent it fills: `
{count()}
` compiles to `insert(el, count, undefined, undefined, { name: "div.children" })`; a static child (a component call, a literal) creates no effect and gets no name. A spread passes the tag as its trailing argument (`spread(el, props, false, undefined, "div")`), and the runtime labels its attribute effect `div.spread` and its children insert `div.children`. DOM output only. +- `locations`: where the JSX is written, beside each label the other two kinds emit, as `file:line:col` — the module path relative to [`root`](#root) with `/` separators, line and column 1-based (the stack-trace convention, so a dev server links `http://localhost:5173/src/App.tsx:18:3` to the right place). A component call gets it as a fourth argument: `` compiles to `createComponent(Home, props, "Home", "src/App.tsx:12:5")`, the location of ` { const value = config.sourceNames ?? config.dev; - if (typeof value === "boolean") return { components: value, bindings: value }; - return { components: value.components ?? config.dev, bindings: value.bindings ?? config.dev }; + if (typeof value === "boolean") return { components: value, bindings: value, locations: value }; + return { + components: value.components ?? config.dev, + bindings: value.bindings ?? config.dev, + locations: value.locations ?? config.dev + }; } /** diff --git a/packages/babel-plugin/src/dom/element.ts b/packages/babel-plugin/src/dom/element.ts index 7611fbd4be..0d52e5b539 100644 --- a/packages/babel-plugin/src/dom/element.ts +++ b/packages/babel-plugin/src/dom/element.ts @@ -38,6 +38,7 @@ import { import { transformNode } from "../shared/transform"; import { decodedAttrValue } from "../universal/element"; import { namesBindings } from "../config"; +import { bindingOptions, sourceLocation } from "../shared/location"; import { InlineElements, BlockElements } from "./constants"; import type { BabelPath, @@ -713,6 +714,8 @@ function transformAttributes( t.jsxExpressionContainer(p.value as babelTypes.Expression) ); (newAttr as babelTypes.JSXAttribute & { _styleProperty?: boolean })._styleProperty = true; + // The split-out property is located at the `style` attribute it came from. + newAttr.loc = styleObjectAttribute.node.loc; path .get("openingElement") .node.attributes.splice(Number(styleObjectAttribute.key) + ++i, 0, newAttr); @@ -763,13 +766,16 @@ function transformAttributes( ) ) { classArrayAttribute.node.value = t.stringLiteral(staticClasses.join(" ")); + const dynamicClassAttribute = t.jsxAttribute( + t.jsxIdentifier("class"), + t.jsxExpressionContainer(dynamicClassElement) + ); + // `sourceNames.locations`: the split-out object locates at the array + // attribute it came from. + dynamicClassAttribute.loc = classArrayAttribute.node.loc; path .get("openingElement") - .node.attributes.splice( - Number(classArrayAttribute.key) + 1, - 0, - t.jsxAttribute(t.jsxIdentifier("class"), t.jsxExpressionContainer(dynamicClassElement)) - ); + .node.attributes.splice(Number(classArrayAttribute.key) + 1, 0, dynamicClassAttribute); } } @@ -815,6 +821,8 @@ function transformAttributes( t.jsxExpressionContainer(p.value as babelTypes.Expression) ); (newAttr as babelTypes.JSXAttribute & { _classProperty?: boolean })._classProperty = true; + // The split-out property is located at the `class` attribute it came from. + newAttr.loc = classListAttribute.node.loc; path .get("openingElement") .node.attributes.splice(Number(classListAttribute.key) + ++i, 0, newAttr); @@ -1223,7 +1231,11 @@ function transformAttributes( tagName, checkableInput, styleProperty: isStyleProperty, - classProperty: isClassProperty + classProperty: isClassProperty, + // `sourceNames.locations`: the attribute's start (for a property + // split out of a `style={{…}}` / `class={{…}}` object, the + // object attribute's start — see the pre-passes above). + loc: sourceLocation(attribute, attribute.node) }); } else { results.exprs.push( @@ -1384,6 +1396,10 @@ function transformChildren( if (!transformed) return memo; (transformed as TransformResult & { allocatesIds?: boolean }).allocatesIds = allocatesIds; (transformed as TransformResult & { functionHole?: boolean }).functionHole = functionHole; + // `sourceNames.locations`: where the hole is written, for its insert + // (a spread child `{...items()}` is a hole too). + if (child.isJSXExpressionContainer() || child.isJSXSpreadChild()) + transformed.loc = sourceLocation(child, child.node); const i = memo.length; if (transformed.text && i && memo[i - 1].text) { memo[i - 1].template = @@ -1467,14 +1483,11 @@ function transformChildren( // is inserted directly and creates no effect to name. const accessorHole = child.dynamic || (child as TransformResult & { functionHole?: boolean }).functionHole; + // `sourceNames.locations`: `loc` after `name`, the hole's `{` start. const emitInsert = (args: babelTypes.Expression[]) => { if (accessorHole && namesBindings(getConfig(path))) { while (args.length < 4) args.push(t.identifier("undefined")); - args.push( - t.objectExpression([ - t.objectProperty(t.identifier("name"), t.stringLiteral(`${tagName}.children`)) - ]) - ); + args.push(bindingOptions(`${tagName}.children`, child.loc)); } results.exprs.push(t.expressionStatement(t.callExpression(insert, args))); }; @@ -1774,7 +1787,14 @@ function processSpreads( // `sourceNames.bindings`: the tag as written rides as spread's trailing // argument (past the runtime-only `skip` slot); the runtime labels its // attribute effect `.spread` and its children insert `.children`. - if (namesBindings(config)) args.push(t.identifier("undefined"), t.stringLiteral(tagName)); + // `sourceNames.locations`: the first spread attribute's start (`{...`) + // follows the name as one more string argument. + if (namesBindings(config)) { + args.push(t.identifier("undefined"), t.stringLiteral(tagName)); + const spread = attributes.find(a => t.isJSXSpreadAttribute(a.node)); + const loc = spread && sourceLocation(spread, spread.node); + if (loc !== undefined) args.push(t.stringLiteral(loc)); + } return [ filteredAttributes, diff --git a/packages/babel-plugin/src/dom/template.ts b/packages/babel-plugin/src/dom/template.ts index 189b2dbd58..c1d5aa98f7 100644 --- a/packages/babel-plugin/src/dom/template.ts +++ b/packages/babel-plugin/src/dom/template.ts @@ -12,6 +12,7 @@ import { } from "../shared/utils"; import { setAttr } from "./element"; import { namesBindings } from "../config"; +import { bindingOptions } from "../shared/location"; import type { NodePath } from "@babel/traverse"; import type { DynamicBinding, ProgramScopeData, TemplateRecord, TransformResult } from "../types"; @@ -218,21 +219,19 @@ function wrapDynamics( // binding's `.` as written (undoing the `prop:` the locked // DOM property pre-pass added to `value`, `checked`, …), the merged effect // listing all of its bindings — as a trailing `{ name }` options argument. + // `sourceNames.locations` adds `loc` after it: the first binding's + // attribute start (a merged effect points at the first attribute it lists). const label = namesBindings(config) ? [ - t.objectExpression([ - t.objectProperty( - t.identifier("name"), - t.stringLiteral( - dynamics - .map( - d => - `${d.tagName}.${isLockedDOMProperty(d.tagName, d.key) ? d.key.replace(/^prop:/, "") : d.key}` - ) - .join(", ") + bindingOptions( + dynamics + .map( + d => + `${d.tagName}.${isLockedDOMProperty(d.tagName, d.key) ? d.key.replace(/^prop:/, "") : d.key}` ) - ) - ]) + .join(", "), + dynamics.find(d => d.loc !== undefined)?.loc + ) ] : []; diff --git a/packages/babel-plugin/src/shared/component.ts b/packages/babel-plugin/src/shared/component.ts index c43b861ee5..1db09933c8 100644 --- a/packages/babel-plugin/src/shared/component.ts +++ b/packages/babel-plugin/src/shared/component.ts @@ -14,6 +14,7 @@ import { transformNode, getCreateTemplate, thisTagIdentifiers } from "./transfor import { markPropsLiteral } from "../ssr/props"; import { decodedAttrValue } from "../universal/element"; import { sourceNames, type PluginConfig } from "../config"; +import { sourceLocation } from "./location"; import type { BabelPath, JSXNode, TransformResult } from "../types"; type JSXAttributePath = BabelPath; @@ -370,6 +371,10 @@ export default function transformComponent( sourceNames(config).components && (config.generate === "dom" || config.generate === "ssr"); if (labelled) { componentArgs.push(t.stringLiteral(tagName)); + // `sourceNames.locations`: the opening element's start (`::`, + * the file relative to `config.root` (default: Babel's `cwd`, the working + * directory) with posix separators, line and column both 1-based — the + * stack-trace / editor convention, so a browser links + * `http://localhost:5173/src/App.tsx:18:3` to the right place. Babel's + * `loc.start.column` is 0-based, so it is bumped by one here (the refresh + * pass's `location` keeps Babel's 0-based column; that is a different + * consumer). A file outside `root` yields a path starting with `..`, emitted + * as-is. `undefined` when the kind is off, when Babel was given no `filename` + * for the module, or when `node` is synthetic and carries no `loc`. + * + * `@solidjs/compiler` computes the same string with `relative_id` so both + * compilers print identical output. + */ +export function sourceLocation(path: NodePath, node: t.Node): string | undefined { + const config = getConfig(path); + if (!sourceNames(config).locations) return; + const start = node.loc?.start; + if (!start) return; + const opts = (path.hub as unknown as BabelHubWithMetadata).file?.opts; + const filename = opts?.filename; + if (!filename) return; + const cwd = opts?.cwd ?? ""; + const root = config.root ? resolve(cwd, config.root) : resolve(cwd, cwd); + return `${relative(root, resolve(cwd, filename))}:${start.line}:${start.column + 1}`; +} + +/** + * A binding's `{ name, loc? }` options literal — `name` first, then `loc` + * only when there is one, so both JSX compilers print the same text. + */ +export function bindingOptions(name: string, loc: string | undefined): t.ObjectExpression { + const properties = [t.objectProperty(t.identifier("name"), t.stringLiteral(name))]; + if (loc !== undefined) + properties.push(t.objectProperty(t.identifier("loc"), t.stringLiteral(loc))); + return t.objectExpression(properties); +} + +// Path arithmetic without node's `path` (this plugin bundles for any host): +// the same steps as `@solidjs/compiler`'s `relative_id` — resolve both paths +// against `cwd`, fold `.`/`..`, drop the common prefix. + +/** `p` resolved against `base` (posix separators, `.`/`..` folded), as segments. */ +function resolve(base: string, p: string): string[] { + const posix = p.replace(/\\/g, "/"); + const absolute = /^(\/|[A-Za-z]:\/)/.test(posix); + const joined = absolute ? posix : `${base.replace(/\\/g, "/")}/${posix}`; + const segments: string[] = []; + for (const segment of joined.split("/")) { + if (segment === "." || (segment === "" && segments.length)) continue; + if (segment === "..") { + // Never past the root marker (`""` for `/…`, `C:` for a drive). + if (segments.length > 1) segments.pop(); + continue; + } + segments.push(segment); + } + return segments; +} + +/** `file` relative to `root`, both resolved: `..` per root segment left, then the file's rest. */ +function relative(root: string[], file: string[]): string { + let i = 0; + while (i < root.length && i < file.length && root[i] === file[i]) i++; + return [...root.slice(i).map(() => ".."), ...file.slice(i)].join("/"); +} diff --git a/packages/babel-plugin/src/types.ts b/packages/babel-plugin/src/types.ts index 200ebd2e4b..f483d679d3 100644 --- a/packages/babel-plugin/src/types.ts +++ b/packages/babel-plugin/src/types.ts @@ -62,6 +62,8 @@ export type TsrxBabelAst = t.File & { export type BabelFileWithMetadata = { ast: TsrxBabelAst; + /** Babel's normalized file options: `filename` is absent when the caller gave none. */ + opts?: { filename?: string | null; cwd?: string }; metadata: { config?: PluginConfig; css?: string; @@ -110,6 +112,8 @@ export interface TransformResult { spreadElement?: boolean; groupable?: Set; groupId?: t.Identifier; + /** `sourceNames.locations`: a hole's `{` start, `file:line:col` (shared/location.ts). */ + loc?: string; } export type TransformNodeResult = TransformResult | null | undefined; @@ -162,6 +166,8 @@ export interface DynamicBinding { styleProperty?: boolean; classProperty?: boolean; checkableInput?: boolean; + /** `sourceNames.locations`: the binding's attribute start, `file:line:col` (shared/location.ts). */ + loc?: string; } export interface SetAttrOptions { diff --git a/packages/babel-plugin/test/__dom_hydratable_dev_fixtures__/walkValidation/output.js b/packages/babel-plugin/test/__dom_hydratable_dev_fixtures__/walkValidation/output.js index 97ec3341a7..a1709eae52 100644 --- a/packages/babel-plugin/test/__dom_hydratable_dev_fixtures__/walkValidation/output.js +++ b/packages/babel-plugin/test/__dom_hydratable_dev_fixtures__/walkValidation/output.js @@ -17,7 +17,8 @@ _$insert( undefined, undefined, { - name: "span.children" + name: "span.children", + loc: "test/__dom_hydratable_dev_fixtures__/walkValidation/code.js:5:11" } ); const singleChild = _el$; @@ -30,7 +31,8 @@ _$insert( undefined, undefined, { - name: "main.children" + name: "main.children", + loc: "test/__dom_hydratable_dev_fixtures__/walkValidation/code.js:12:11" } ); const siblingElements = _el$3; @@ -43,7 +45,8 @@ _$insert( undefined, undefined, { - name: "b.children" + name: "b.children", + loc: "test/__dom_hydratable_dev_fixtures__/walkValidation/code.js:19:14" } ); const mixedTextAndElements = _el$6; @@ -56,7 +59,8 @@ _$insert( undefined, undefined, { - name: "li.children" + name: "li.children", + loc: "test/__dom_hydratable_dev_fixtures__/walkValidation/code.js:26:11" } ); const nestedWalk = _el$9; diff --git a/packages/babel-plugin/test/__dom_source_names_fixtures__/bindings/output.js b/packages/babel-plugin/test/__dom_source_names_fixtures__/bindings/output.js index ebf5b35701..1541398d62 100644 --- a/packages/babel-plugin/test/__dom_source_names_fixtures__/bindings/output.js +++ b/packages/babel-plugin/test/__dom_source_names_fixtures__/bindings/output.js @@ -26,7 +26,8 @@ _$effect( _el$2.data = _v$; }, { - name: "span.textContent" + name: "span.textContent", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:2:22" } ); // One binding → its own effect, named `.`. @@ -38,7 +39,8 @@ _$effect( _el$3.value = _v$ ?? ""; }, { - name: "input.value" + name: "input.value", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:3:21" } ); const attr = _el$3; @@ -49,7 +51,8 @@ _$effect( _$className(_el$4, _v$, _$p); }, { - name: "div.class" + name: "div.class", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:4:18" } ); const cls = _el$4; @@ -64,7 +67,8 @@ _$effect( t !== _p$?.t && _$setStyleProperty(_el$5, "font-size", t); }, { - name: "div.style:color, div.style:font-size" + name: "div.style:color, div.style:font-size", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:5:20" } ); const style = _el$5; @@ -77,7 +81,8 @@ _$effect( _$setAttribute(_el$6, "classList", _v$); }, { - name: "div.classList" + name: "div.classList", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:6:24" } ); const classList = _el$6; @@ -92,7 +97,8 @@ _$effect( t !== _p$?.t && _$setAttribute(_el$7, "style:width", t); }, { - name: "div.class:selected, div.style:width" + name: "div.class:selected, div.style:width", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:7:17" } ); const ns = _el$7; @@ -115,7 +121,8 @@ _$effect( (!_p$ || o !== _p$.o) && (_el$0.data = o); }, { - name: "button.class, button.title, button.disabled, span.textContent" + name: "button.class, button.title, button.disabled, span.textContent", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:11:11" } ); const merged = _el$8; @@ -123,7 +130,8 @@ const merged = _el$8; // Holes → insert, named for the parent it fills. var _el$1 = _tmpl$3(); _$insert(_el$1, count, undefined, undefined, { - name: "div.children" + name: "div.children", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:17:19" }); const hole = _el$1; var _el$10 = _tmpl$5(), @@ -133,28 +141,49 @@ var _el$10 = _tmpl$5(), _el$13 = _el$12.nextSibling, _el$15 = _el$13.nextSibling; _$insert(_el$10, name, _el$14, undefined, { - name: "div.children" + name: "div.children", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:20:11" }); _$insert(_el$13, greeting, undefined, undefined, { - name: "p.children" + name: "p.children", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:20:23" }); _$insert(_el$10, list, _el$15, undefined, { - name: "div.children" + name: "div.children", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:21:5" }); const holes = _el$10; const staticChild = _tmpl$6(); var _el$17 = _tmpl$3(); -_$insert(_el$17, _$createComponent(Child, {}, "Child")); +_$insert( + _el$17, + _$createComponent(Child, {}, "Child", "test/__dom_source_names_fixtures__/bindings/code.js:27:5") +); const componentChild = _el$17; // Spreads → the tag rides as spread's trailing argument. var _el$18 = _tmpl$3(); -_$spread(_el$18, props, false, undefined, "div"); +_$spread( + _el$18, + props, + false, + undefined, + "div", + "test/__dom_source_names_fixtures__/bindings/code.js:32:21" +); const spread = _el$18; var _el$19 = _tmpl$7(); -_$spread(_el$19, props, true, undefined, "section"); +_$spread( + _el$19, + props, + true, + undefined, + "section", + "test/__dom_source_names_fixtures__/bindings/code.js:33:37" +); _$insert(_el$19, children, undefined, undefined, { - name: "section.children" + name: "section.children", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:33:48" }); const spreadWithChildren = _el$19; var _el$20 = _tmpl$8(); @@ -173,7 +202,8 @@ _$spread( ], false, undefined, - "a" + "a", + "test/__dom_source_names_fixtures__/bindings/code.js:34:38" ); const spreadMixed = _el$20; @@ -191,7 +221,8 @@ _$effect( _$claimElement(_el$21, _o$); }, { - name: "a.href, a.target" + name: "a.href, a.target", + loc: "test/__dom_source_names_fixtures__/bindings/code.js:38:6" } ); const link = _el$21; diff --git a/packages/babel-plugin/test/__dom_source_names_fixtures__/components/output.js b/packages/babel-plugin/test/__dom_source_names_fixtures__/components/output.js index 727a67333e..86f156b03d 100644 --- a/packages/babel-plugin/test/__dom_source_names_fixtures__/components/output.js +++ b/packages/babel-plugin/test/__dom_source_names_fixtures__/components/output.js @@ -13,7 +13,8 @@ const Component = () => { name: "John" }, - "Child" + "Child", + "test/__dom_source_names_fixtures__/components/code.js:3:25" ); var _el$ = _tmpl$2(), _el$3 = _el$.firstChild, @@ -36,13 +37,15 @@ _$insert( get children() { var _el$2 = _tmpl$(); _$insert(_el$2, name, undefined, undefined, { - name: "span.children" + name: "span.children", + loc: "test/__dom_source_names_fixtures__/components/code.js:8:13" }); return _el$2; } } ), - "Child" + "Child", + "test/__dom_source_names_fixtures__/components/code.js:7:5" ), _el$3 ); @@ -53,7 +56,8 @@ _$insert( { variant: "primary" }, - "Ui.Button" + "Ui.Button", + "test/__dom_source_names_fixtures__/components/code.js:10:5" ), _el$4 ); @@ -65,7 +69,8 @@ _$insert( cols: 2, children: "text" }, - "Ui.Layout.Grid" + "Ui.Layout.Grid", + "test/__dom_source_names_fixtures__/components/code.js:11:5" ), _el$5 ); @@ -83,10 +88,12 @@ _$insert( { item: item }, - "Row" + "Row", + "test/__dom_source_names_fixtures__/components/code.js:12:33" ) }, - "For" + "For", + "test/__dom_source_names_fixtures__/components/code.js:12:5" ), _el$6 ); @@ -99,22 +106,44 @@ _$insert( return visible(); }, get children() { - return _$createComponent(Child, {}, "Child"); + return _$createComponent( + Child, + {}, + "Child", + "test/__dom_source_names_fixtures__/components/code.js:14:7" + ); } }, - "Show" + "Show", + "test/__dom_source_names_fixtures__/components/code.js:13:5" ), _el$7 ); -_$insert(_el$, _$createComponent(_self$.Row, {}, "this.Row"), _el$8); +_$insert( + _el$, + _$createComponent( + _self$.Row, + {}, + "this.Row", + "test/__dom_source_names_fixtures__/components/code.js:16:5" + ), + _el$8 +); _$insert( _el$, _$createComponent( Comp, { - children: () => _$createComponent(Child, {}, "Child") + children: () => + _$createComponent( + Child, + {}, + "Child", + "test/__dom_source_names_fixtures__/components/code.js:17:18" + ) }, - "Comp" + "Comp", + "test/__dom_source_names_fixtures__/components/code.js:17:5" ), _el$9 ); @@ -124,6 +153,11 @@ const template = (() => { })(); class Container { render() { - return _$createComponent(this.Row, {}, "this.Row"); + return _$createComponent( + this.Row, + {}, + "this.Row", + "test/__dom_source_names_fixtures__/components/code.js:23:12" + ); } } diff --git a/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/code.js b/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/code.js new file mode 100644 index 0000000000..63fead893c --- /dev/null +++ b/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/code.js @@ -0,0 +1,27 @@ +// `sourceNames.locations`: every label carries where its JSX is written, +// `file:line:col` relative to `root` (here the fixtures directory, resolved +// against the working directory), line and column 1-based. +import { Layout, Card, Row } from "./components"; + +// Nested components → each `createComponent` points at its own ` + + + + +); + +// An attribute binding → the attribute's start; a merged effect points at +// the first binding it lists. +const attribute =
; +const styleObject =
; + +// A hole → the `{` of the expression. +const hole =

Count: {count()}

; + +// A spread → the `{...` of the first spread attribute. +const spread =
; + +// A component inside a conditional → the conditional's memo keeps the label. +const conditional =
{visible() ? : null}
; diff --git a/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/options.json b/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/options.json new file mode 100644 index 0000000000..87d887988f --- /dev/null +++ b/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/options.json @@ -0,0 +1,3 @@ +{ + "root": "test/__dom_source_names_fixtures__" +} diff --git a/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/output.js b/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/output.js new file mode 100644 index 0000000000..1ed3687412 --- /dev/null +++ b/packages/babel-plugin/test/__dom_source_names_fixtures__/locations/output.js @@ -0,0 +1,111 @@ +import { template as _$template } from "r-dom"; +import { memo as _$memo } from "r-dom"; +import { spread as _$spread } from "r-dom"; +import { insert as _$insert } from "r-dom"; +import { setStyleProperty as _$setStyleProperty } from "r-dom"; +import { setAttribute as _$setAttribute } from "r-dom"; +import { className as _$className } from "r-dom"; +import { readShallow as _$readShallow } from "r-dom"; +import { effect as _$effect } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; +var _tmpl$ = /*#__PURE__*/ _$template(`
`), + _tmpl$2 = /*#__PURE__*/ _$template(`

Count: `), + _tmpl$3 = /*#__PURE__*/ _$template(`

`); +// `sourceNames.locations`: every label carries where its JSX is written, +// `file:line:col` relative to `root` (here the fixtures directory, resolved +// against the working directory), line and column 1-based. +import { Layout, Card, Row } from "./components"; + +// Nested components → each `createComponent` points at its own ` ({ + e: _$readShallow(cls()), + t: title() + }), + ({ e, t }, _p$) => { + _$className(_el$, e, _p$?.e); + t !== _p$?.t && _$setAttribute(_el$, "title", t); + }, + { + name: "div.class, div.title", + loc: "locations/code.js:17:24" + } +); +const attribute = _el$; +var _el$2 = _tmpl$(); +_$effect( + () => color(), + _v$ => { + _$setStyleProperty(_el$2, "color", _v$); + }, + { + name: "div.style:color", + loc: "locations/code.js:18:26" + } +); +const styleObject = _el$2; + +// A hole → the `{` of the expression. +var _el$3 = _tmpl$2(), + _el$4 = _el$3.firstChild; +_$insert(_el$3, count, null, undefined, { + name: "p.children", + loc: "locations/code.js:21:24" +}); +const hole = _el$3; + +// A spread → the `{...` of the first spread attribute. +var _el$5 = _tmpl$3(); +_$spread(_el$5, props, false, undefined, "section", "locations/code.js:24:25"); +const spread = _el$5; + +// A component inside a conditional → the conditional's memo keeps the label. +var _el$6 = _tmpl$(); +_$insert( + _el$6, + (() => { + var _c$ = _$memo(() => !!visible()); + return () => + _c$() + ? _$createComponent( + Card, + { + title: "On" + }, + "Card", + "locations/code.js:27:39" + ) + : null; + })(), + undefined, + undefined, + { + name: "div.children", + loc: "locations/code.js:27:26" + } +); +const conditional = _el$6; diff --git a/packages/babel-plugin/test/__dom_source_names_fixtures__/ssr/output.js b/packages/babel-plugin/test/__dom_source_names_fixtures__/ssr/output.js index f25c391b37..7db48fbd51 100644 --- a/packages/babel-plugin/test/__dom_source_names_fixtures__/ssr/output.js +++ b/packages/babel-plugin/test/__dom_source_names_fixtures__/ssr/output.js @@ -14,7 +14,8 @@ const Component = () => { name: "John" }, - "Child" + "Child", + "test/__dom_source_names_fixtures__/ssr/code.js:3:25" ); var _v$2 = _$escape( _$createComponent( @@ -30,7 +31,8 @@ var _v$2 = _$escape( } } ), - "Child" + "Child", + "test/__dom_source_names_fixtures__/ssr/code.js:7:5" ) ), _v$3 = _$escape( @@ -39,7 +41,8 @@ var _v$2 = _$escape( { variant: "primary" }, - "Ui.Button" + "Ui.Button", + "test/__dom_source_names_fixtures__/ssr/code.js:10:5" ) ), _v$4 = _$escape( @@ -49,7 +52,8 @@ var _v$2 = _$escape( cols: 2, children: "text" }, - "Ui.Layout.Grid" + "Ui.Layout.Grid", + "test/__dom_source_names_fixtures__/ssr/code.js:11:5" ) ), _v$5 = _$escape( @@ -65,10 +69,12 @@ var _v$2 = _$escape( { item: item }, - "Row" + "Row", + "test/__dom_source_names_fixtures__/ssr/code.js:12:33" ) }, - "For" + "For", + "test/__dom_source_names_fixtures__/ssr/code.js:12:5" ) ), _v$6 = _$escape( @@ -79,20 +85,40 @@ var _v$2 = _$escape( return visible(); }, get children() { - return _$createComponent(Child, {}, "Child"); + return _$createComponent( + Child, + {}, + "Child", + "test/__dom_source_names_fixtures__/ssr/code.js:14:7" + ); } }, - "Show" + "Show", + "test/__dom_source_names_fixtures__/ssr/code.js:13:5" + ) + ), + _v$7 = _$escape( + _$createComponent( + _self$.Row, + {}, + "this.Row", + "test/__dom_source_names_fixtures__/ssr/code.js:16:5" ) ), - _v$7 = _$escape(_$createComponent(_self$.Row, {}, "this.Row")), _v$8 = _$escape( _$createComponent( Comp, { - children: () => _$createComponent(Child, {}, "Child") + children: () => + _$createComponent( + Child, + {}, + "Child", + "test/__dom_source_names_fixtures__/ssr/code.js:17:18" + ) }, - "Comp" + "Comp", + "test/__dom_source_names_fixtures__/ssr/code.js:17:5" ) ); const template = (() => { @@ -101,6 +127,11 @@ const template = (() => { })(); class Container { render() { - return _$createComponent(this.Row, {}, "this.Row"); + return _$createComponent( + this.Row, + {}, + "this.Row", + "test/__dom_source_names_fixtures__/ssr/code.js:23:12" + ); } } diff --git a/packages/babel-plugin/test/dom-source-names.spec.js b/packages/babel-plugin/test/dom-source-names.spec.js index ff0375d5e2..8f11945716 100644 --- a/packages/babel-plugin/test/dom-source-names.spec.js +++ b/packages/babel-plugin/test/dom-source-names.spec.js @@ -22,7 +22,8 @@ runFixtures({ describe("sourceNames defaults", () => { const babel = require("@babel/core"); const code = "const view =
;"; - const label = '_$createComponent(Home, {}, "Home")'; + // A prefix: with `locations` (also following `dev`) a fourth argument follows. + const label = '_$createComponent(Home, {}, "Home"'; const binding = 'name: "div.class"'; const compile = opts => babel.transformSync(code, { @@ -59,3 +60,64 @@ describe("sourceNames defaults", () => { expect(prodPicked).not.toContain(binding); }); }); + +// `sourceNames.locations` follows `dev` like the other kinds; a location is +// only emitted beside a label, and never without a `filename`. +describe("sourceNames locations defaults", () => { + const babel = require("@babel/core"); + const code = "const view =
;"; + const label = '_$createComponent(Home, {}, "Home", "input.jsx:1:33")'; + const binding = 'loc: "input.jsx:1:19"'; + const compile = (opts, filename = "input.jsx") => + babel.transformSync(code, { + configFile: false, + babelrc: false, + ...(filename ? { filename } : {}), + plugins: [[plugin, { moduleName: "r-dom", ...opts }]] + }).code; + + test("unset follows dev: on in dev, off otherwise", () => { + const dev = compile({ dev: true }); + expect(dev).toContain(label); + expect(dev).toContain(binding); + const prod = compile({ dev: false }); + expect(prod).not.toContain("input.jsx"); + expect(prod).not.toContain("loc:"); + expect(prod).toBe(compile({})); + }); + + test("`{ locations: false }` in dev drops only the locations", () => { + const named = compile({ dev: true, sourceNames: { locations: false } }); + expect(named).toContain('_$createComponent(Home, {}, "Home")'); + expect(named).toContain('name: "div.class"'); + expect(named).not.toContain("input.jsx"); + expect(named).not.toContain("loc:"); + expect(named).toBe( + compile({ dev: true, sourceNames: { components: true, bindings: true, locations: false } }) + ); + }); + + test("a location rides only with its label", () => { + const components = compile({ dev: false, sourceNames: { components: true, locations: true } }); + expect(components).toContain(label); + expect(components).not.toContain("name:"); + expect(components).not.toContain("loc:"); + const bindings = compile({ dev: false, sourceNames: { bindings: true, locations: true } }); + expect(bindings).toContain(binding); + expect(bindings).not.toContain('"Home"'); + }); + + test("no `filename` → no location, even with the kind on", () => { + const out = compile({ dev: true, sourceNames: true }, null); + expect(out).toContain('_$createComponent(Home, {}, "Home")'); + expect(out).toContain('name: "div.class"'); + expect(out).not.toContain("loc:"); + }); + + test("the path is relative to `root`, posix, with `..` outside it", () => { + const abs = require("path").resolve("src/App.jsx"); + expect(compile({ dev: true }, abs)).toContain('"src/App.jsx:1:33"'); + expect(compile({ dev: true, root: "src" }, abs)).toContain('"App.jsx:1:33"'); + expect(compile({ dev: true, root: "test" }, abs)).toContain('"../src/App.jsx:1:33"'); + }); +}); diff --git a/packages/compiler/README.md b/packages/compiler/README.md index ca3361d659..238f1699b5 100644 --- a/packages/compiler/README.md +++ b/packages/compiler/README.md @@ -113,7 +113,8 @@ Pass `sourceMap: true` to receive a JSON source map string in `result.map`. For - `generate`: `"dom"`, `"ssr"`, `"universal"`, or `"dynamic"` (default `"dom"`) - `hydratable` - `dev` -- `sourceNames` (`boolean | { components?: boolean; bindings?: boolean }`, default follows `dev`): names as written in source, carried into output so dev/observe runtimes can label the reactive graph after minification. Unset, every kind is on when `dev: true` and off otherwise; `true`/`false` sets every kind (`sourceNames: false` opts a dev build out); the object form picks, and each kind it leaves unspecified follows `dev`. Production output (`dev: false`) is byte-identical with the option set or unset. Same shape and defaults as `@solidjs/babel-plugin`'s option. `components` emits the source tag name as `createComponent`'s third argument (`createComponent(Home, props, "Home")`) — DOM and SSR output (SSR keeps the `createComponent` call it otherwise inlines to `Comp(props)`); not universal or dynamic. `bindings` names every compiled binding effect by what it writes — `effect(…, { name: "span.textContent" })`, a hole `insert(el, v, undefined, undefined, { name: "div.children" })`, a spread `spread(el, props, false, undefined, "div")` (labelled `div.spread` / `div.children` by the runtime) — DOM output only. The production runtimes ignore the names. These are the JSX-level kinds only: naming the primitives themselves is the separate `transformSourceNames` pass below, which the build tool runs on every module independently of the JSX compiler +- `root` (`string`, default the working directory): the project root `sourceNames.locations` paths (and `transformSourceNames`'s `loc`) are made relative to — the same meaning as `transformDirectives`'s `root`. +- `sourceNames` (`boolean | { components?: boolean; bindings?: boolean; locations?: boolean }`, default follows `dev`): names as written in source, carried into output so dev/observe runtimes can label the reactive graph after minification. Unset, every kind is on when `dev: true` and off otherwise; `true`/`false` sets every kind (`sourceNames: false` opts a dev build out); the object form picks, and each kind it leaves unspecified follows `dev`. Production output (`dev: false`) is byte-identical with the option set or unset. Same shape and defaults as `@solidjs/babel-plugin`'s option. `components` emits the source tag name as `createComponent`'s third argument (`createComponent(Home, props, "Home")`) — DOM and SSR output (SSR keeps the `createComponent` call it otherwise inlines to `Comp(props)`); not universal or dynamic. `bindings` names every compiled binding effect by what it writes — `effect(…, { name: "span.textContent" })`, a hole `insert(el, v, undefined, undefined, { name: "div.children" })`, a spread `spread(el, props, false, undefined, "div")` (labelled `div.spread` / `div.children` by the runtime) — DOM output only. `locations` adds the source position beside each name the other two kinds emit, as `::` with the path relative to `root` (posix separators, `..` when the file is outside the root) and the column 1-based (the stack-trace and editor convention, so `http://localhost:5173/src/App.tsx:18:3` links to the right place): `createComponent(Home, props, "Home", "src/App.tsx:12:5")` — the start of the opening element — and `loc` right after `name` in a binding's options, `effect(…, { name: "div.class", loc: "src/App.tsx:14:7" })` — the attribute's start, a hole's `{`, a spread's `{...props}` (a spread's location rides as one more positional string after the tag). Nothing is emitted without a `filename`. The production runtimes ignore the names. These are the JSX-level kinds only: naming the primitives themselves is the separate `transformSourceNames` pass below, which the build tool runs on every module independently of the JSX compiler - `sourceMap` - `contextToCustomElements` (default `true`) - `delegateEvents` @@ -162,7 +163,7 @@ The runtime module defaults to `@solidjs/web/server-functions`. Function IDs are ### Source names for primitives -`transformSourceNames(code, { filename?, sourceMap? })` is the pass behind `@solidjs/vite-plugin`'s `sourceNames.primitives` option: it names reactive primitives after the identifier they are declared as, so the dev and observe runtimes label graph nodes `count` / `doubled` / `todos.title` instead of `signal` / `computed` / `store.title`. It is plain JavaScript in and out (JSX passes through untouched), which is why it is its own pass rather than a `transform()` option — primitives live in `.ts`/`.js` modules as much as in components. This standalone pass is the single owner of primitive naming: the build tool runs it on every module — `.ts`, `.js`, and JSX files alike — independently of which JSX compiler (this one or `@solidjs/babel-plugin`) handles the file's JSX, so there is no Babel counterpart and `transform()`'s `sourceNames` covers only the JSX-level kinds. `@solidjs/vite-plugin` (solid-vite-plugin #371, `solid: { sourceNames }`) runs it ahead of the JSX transform for the dev and `observe` postures. +`transformSourceNames(code, { filename?, locations?, root?, sourceMap? })` is the pass behind `@solidjs/vite-plugin`'s `sourceNames.primitives` option: it names reactive primitives after the identifier they are declared as, so the dev and observe runtimes label graph nodes `count` / `doubled` / `todos.title` instead of `signal` / `computed` / `store.title`. With `locations` (default `true` — the pass only runs for the dev and observe postures) and a `filename`, the options literal also carries the call's position right after the name, `createSignal(0, { name: "count", loc: "src/store.ts:3:22" })` (`filename` relative to `root`, column 1-based); an explicit `name` is kept and `loc` still added, an explicit `loc` is never overridden. It is plain JavaScript in and out (JSX passes through untouched), which is why it is its own pass rather than a `transform()` option — primitives live in `.ts`/`.js` modules as much as in components. This standalone pass is the single owner of primitive naming: the build tool runs it on every module — `.ts`, `.js`, and JSX files alike — independently of which JSX compiler (this one or `@solidjs/babel-plugin`) handles the file's JSX, so there is no Babel counterpart and `transform()`'s `sourceNames` covers only the JSX-level kinds. `@solidjs/vite-plugin` (solid-vite-plugin #371, `solid: { sourceNames }`) runs it ahead of the JSX transform for the dev and `observe` postures. ```js const [count, setCount] = createSignal(0); // createSignal(0, { name: "count" }) diff --git a/packages/compiler/__tests__/cross-mode-parity.test.js b/packages/compiler/__tests__/cross-mode-parity.test.js index f1901c05c3..47480f939f 100644 --- a/packages/compiler/__tests__/cross-mode-parity.test.js +++ b/packages/compiler/__tests__/cross-mode-parity.test.js @@ -70,7 +70,7 @@ function crossDiff(mode, entry) { const { options } = modes[mode]; let babelRaw, babelError; try { - babelRaw = compileBabel(entry.source, options); + babelRaw = compileBabel(entry.source, options, `${entry.id}.jsx`); } catch (err) { babelError = err; } diff --git a/packages/compiler/__tests__/dom-source-names-fixtures.test.js b/packages/compiler/__tests__/dom-source-names-fixtures.test.js index 5bb09500ac..c3c58adad2 100644 --- a/packages/compiler/__tests__/dom-source-names-fixtures.test.js +++ b/packages/compiler/__tests__/dom-source-names-fixtures.test.js @@ -11,6 +11,7 @@ const oxcFixtures = path.resolve(__dirname, "fixtures/dom-source-names"); const fixtureParity = { bindings: "subset", components: "subset", + locations: "subset", ssr: "subset" }; @@ -22,21 +23,38 @@ const suiteOptions = { contextToCustomElements: true }; +// A fixture's source is the Babel plugin's; a copy next to the output +// (`/code.js`, `/options.json`) stands in until the Babel +// fixture lands here. +function fixtureFile(fixture, name) { + const babelFile = path.join(babelFixtures, fixture, name); + return fs.existsSync(babelFile) ? babelFile : path.join(oxcFixtures, fixture, name); +} + function readFixture(name) { - return fs.readFileSync(path.join(babelFixtures, name, "code.js"), "utf8"); + return fs.readFileSync(fixtureFile(name, "code.js"), "utf8"); } // Mirrors the Babel fixture runner's per-fixture `options.json` layering. function fixtureOptions(fixture) { - const file = path.join(babelFixtures, fixture, "options.json"); + const file = fixtureFile(fixture, "options.json"); return fs.existsSync(file) ? { ...suiteOptions, ...JSON.parse(fs.readFileSync(file, "utf8")) } : suiteOptions; } +// A fixture that sets `root` (the `locations` one) is named the way the +// Babel runner names it — `//code.` — so the location +// strings read `locations/code.jsx:8:3`, the Babel snapshot's path up to the +// extension (Babel parses JSX in `.js`; here the extension picks the parser). +function fixtureFilename(fixture, options) { + return options.root ? `${options.root}/${fixture}/code.jsx` : `${fixture}.jsx`; +} + function transformFixture(code, fixture) { + const options = fixtureOptions(fixture); return ( - transform(code, { filename: `${fixture}.jsx`, ...fixtureOptions(fixture) }).code.trimEnd() + + transform(code, { filename: fixtureFilename(fixture, options), ...options }).code.trimEnd() + "\n" ); } @@ -56,12 +74,13 @@ function writeOutputFixture(fixture, output) { describe("AST-native Babel DOM sourceNames fixture reuse", () => { it("classifies supported Babel DOM sourceNames fixtures", () => { - const actual = fs - .readdirSync(babelFixtures, { withFileTypes: true }) - .filter(entry => entry.isDirectory()) - .filter(entry => fs.existsSync(path.join(babelFixtures, entry.name, "code.js"))) - .map(entry => entry.name) - .sort(); + const sources = dir => + fs + .readdirSync(dir, { withFileTypes: true }) + .filter(entry => entry.isDirectory()) + .filter(entry => fs.existsSync(path.join(dir, entry.name, "code.js"))) + .map(entry => entry.name); + const actual = [...new Set([...sources(babelFixtures), ...sources(oxcFixtures)])].sort(); expect(Object.keys(fixtureParity).sort()).toEqual(actual); }); @@ -98,3 +117,72 @@ describe("AST-native Babel DOM sourceNames fixture reuse", () => { expect(code).toContain("Child({"); }); }); + +// `sourceNames.locations` follows `dev` like the other kinds; a location is +// only emitted beside a label, and never without a `filename`. Same source, +// same expectations as the Babel plugin's `sourceNames locations defaults`. +describe("sourceNames locations defaults", () => { + const code = "const view =
;"; + const label = '_$createComponent(Home, {}, "Home", "input.jsx:1:33")'; + const binding = 'loc: "input.jsx:1:19"'; + const compile = (opts, filename = "input.jsx") => + transform(code, { moduleName: "r-dom", ...(filename ? { filename } : {}), ...opts }).code; + + it("unset follows dev: on in dev, off otherwise", () => { + const dev = compile({ dev: true }); + expect(dev).toContain(label); + expect(dev).toContain(binding); + const prod = compile({ dev: false }); + expect(prod).not.toContain("input.jsx"); + expect(prod).not.toContain("loc:"); + expect(prod).toBe(compile({})); + }); + + it("`{ locations: false }` in dev drops only the locations", () => { + const named = compile({ dev: true, sourceNames: { locations: false } }); + expect(named).toContain('_$createComponent(Home, {}, "Home")'); + expect(named).toContain('name: "div.class"'); + expect(named).not.toContain("input.jsx"); + expect(named).not.toContain("loc:"); + expect(named).toBe( + compile({ dev: true, sourceNames: { components: true, bindings: true, locations: false } }) + ); + }); + + it("a location rides only with its label", () => { + const components = compile({ dev: false, sourceNames: { components: true, locations: true } }); + expect(components).toContain(label); + expect(components).not.toContain("name:"); + expect(components).not.toContain("loc:"); + const bindings = compile({ dev: false, sourceNames: { bindings: true, locations: true } }); + expect(bindings).toContain(binding); + expect(bindings).not.toContain('"Home"'); + }); + + it("no `filename` → no location, even with the kind on", () => { + const out = compile({ dev: true, sourceNames: true }, null); + expect(out).toContain('_$createComponent(Home, {}, "Home")'); + expect(out).toContain('name: "div.class"'); + expect(out).not.toContain("loc:"); + }); + + it("the path is relative to `root`, posix, with `..` outside it", () => { + const abs = path.resolve("src/App.jsx"); + expect(compile({ dev: true }, abs)).toContain('"src/App.jsx:1:33"'); + expect(compile({ dev: true, root: "src" }, abs)).toContain('"App.jsx:1:33"'); + expect(compile({ dev: true, root: path.resolve("src") }, abs)).toContain('"App.jsx:1:33"'); + expect(compile({ dev: true, root: "test" }, abs)).toContain('"../src/App.jsx:1:33"'); + }); + + it("SSR output carries the location as the fourth argument too", () => { + const out = compile({ dev: true, generate: "ssr" }); + expect(out).toContain(label); + }); + + it("validates `root` and the `locations` kind", () => { + expect(() => compile({ root: 1 })).toThrow("`root` option must be a string"); + expect(() => compile({ sourceNames: { locations: "yes" } })).toThrow( + "`sourceNames.locations` must be boolean" + ); + }); +}); diff --git a/packages/compiler/__tests__/fixtures/dom-hydratable-dev/walkValidation/output.js b/packages/compiler/__tests__/fixtures/dom-hydratable-dev/walkValidation/output.js index ff78c2dd31..2b907e594b 100644 --- a/packages/compiler/__tests__/fixtures/dom-hydratable-dev/walkValidation/output.js +++ b/packages/compiler/__tests__/fixtures/dom-hydratable-dev/walkValidation/output.js @@ -13,26 +13,38 @@ var _el$ = _$getNextElement(_tmpl$); var _el$2 = _$getFirstChild(_el$, "span"); _$insert(_el$2, _$scope(() => { return name(); -}), undefined, undefined, { name: "span.children" }); +}), undefined, undefined, { + name: "span.children", + loc: "walkValidation.jsx:5:11" +}); const singleChild = _el$; var _el$3 = _$getNextElement(_tmpl$2); var _el$4 = _$getFirstChild(_el$3, "header"); var _el$5 = _$getNextSibling(_el$4, "main"); _$insert(_el$5, _$scope(() => { return name(); -}), undefined, undefined, { name: "main.children" }); +}), undefined, undefined, { + name: "main.children", + loc: "walkValidation.jsx:12:11" +}); const siblingElements = _el$3; var _el$6 = _$getNextElement(_tmpl$3); var _el$7 = _el$6.firstChild; var _el$8 = _$getNextSibling(_el$7, "b"); _$insert(_el$8, _$scope(() => { return name(); -}), undefined, undefined, { name: "b.children" }); +}), undefined, undefined, { + name: "b.children", + loc: "walkValidation.jsx:19:14" +}); const mixedTextAndElements = _el$6; var _el$9 = _$getNextElement(_tmpl$4); var _el$10 = _$getFirstChild(_el$9, "ul"); var _el$11 = _$getFirstChild(_el$10, "li"); _$insert(_el$11, _$scope(() => { return name(); -}), undefined, undefined, { name: "li.children" }); +}), undefined, undefined, { + name: "li.children", + loc: "walkValidation.jsx:26:11" +}); const nestedWalk = _el$9; diff --git a/packages/compiler/__tests__/fixtures/dom-source-names/bindings/output.js b/packages/compiler/__tests__/fixtures/dom-source-names/bindings/output.js index cce665bc3e..7e815e9b94 100644 --- a/packages/compiler/__tests__/fixtures/dom-source-names/bindings/output.js +++ b/packages/compiler/__tests__/fixtures/dom-source-names/bindings/output.js @@ -22,18 +22,27 @@ var _el$ = _tmpl$(); var _el$2 = _el$.firstChild; _$effect(() => label(), (_v$) => { _el$2.data = _v$; -}, { name: "span.textContent" }); +}, { + name: "span.textContent", + loc: "bindings.jsx:2:22" +}); // One binding → its own effect, named `.`. const single = _el$; var _el$3 = _tmpl$2(); _$effect(() => text(), (_v$) => { _el$3.value = _v$ ?? ""; -}, { name: "input.value" }); +}, { + name: "input.value", + loc: "bindings.jsx:3:21" +}); const attr = _el$3; var _el$4 = _tmpl$3(); _$effect(() => _$readShallow(active() ? "on" : "off"), (_v$, _$p) => { _$className(_el$4, _v$, _$p); -}, { name: "div.class" }); +}, { + name: "div.class", + loc: "bindings.jsx:4:18" +}); const cls = _el$4; var _el$5 = _tmpl$3(); _$effect(() => { @@ -44,12 +53,18 @@ _$effect(() => { }, ({ e, t }, _p$) => { e !== _p$?.e && _$setStyleProperty(_el$5, "color", e); t !== _p$?.t && _$setStyleProperty(_el$5, "font-size", t); -}, { name: "div.style:color, div.style:font-size" }); +}, { + name: "div.style:color, div.style:font-size", + loc: "bindings.jsx:5:20" +}); const style = _el$5; var _el$6 = _tmpl$3(); _$effect(() => ({ active: active() }), (_v$) => { _$setAttribute(_el$6, "classList", _v$); -}, { name: "div.classList" }); +}, { + name: "div.classList", + loc: "bindings.jsx:6:24" +}); const classList = _el$6; var _el$7 = _tmpl$3(); _$effect(() => { @@ -60,7 +75,10 @@ _$effect(() => { }, ({ e, t }, _p$) => { e !== _p$?.e && _$setAttribute(_el$7, "class:selected", e); t !== _p$?.t && _$setAttribute(_el$7, "style:width", t); -}, { name: "div.class:selected, div.style:width" }); +}, { + name: "div.class:selected, div.style:width", + loc: "bindings.jsx:7:17" +}); const ns = _el$7; var _el$8 = _tmpl$4(); var _el$9 = _el$8.firstChild; @@ -77,11 +95,17 @@ _$effect(() => { t !== _p$?.t && _$setAttribute(_el$8, "title", t); a !== _p$?.a && _$setAttribute(_el$8, "disabled", a); (!_p$ || o !== _p$.o) && (_el$10.data = o); -}, { name: "button.class, button.title, button.disabled, span.textContent" }); +}, { + name: "button.class, button.title, button.disabled, span.textContent", + loc: "bindings.jsx:11:11" +}); // Several bindings in one template → one merged effect listing all of them. const merged = _el$8; var _el$11 = _tmpl$3(); -_$insert(_el$11, count, undefined, undefined, { name: "div.children" }); +_$insert(_el$11, count, undefined, undefined, { + name: "div.children", + loc: "bindings.jsx:17:19" +}); // Holes → insert, named for the parent it fills. const hole = _el$11; var _el$12 = _tmpl$5(); @@ -90,21 +114,33 @@ var _el$14 = _el$13.nextSibling; var _el$15 = _el$14.nextSibling; var _el$16 = _el$15.nextSibling; var _el$17 = _el$16.nextSibling; -_$insert(_el$12, name, _el$14, undefined, { name: "div.children" }); -_$insert(_el$16, greeting, undefined, undefined, { name: "p.children" }); -_$insert(_el$12, list, _el$17, undefined, { name: "div.children" }); +_$insert(_el$12, name, _el$14, undefined, { + name: "div.children", + loc: "bindings.jsx:20:11" +}); +_$insert(_el$16, greeting, undefined, undefined, { + name: "p.children", + loc: "bindings.jsx:20:23" +}); +_$insert(_el$12, list, _el$17, undefined, { + name: "div.children", + loc: "bindings.jsx:21:5" +}); const holes = _el$12; const staticChild = _tmpl$6(); var _el$19 = _tmpl$3(); -_$insert(_el$19, _$createComponent(Child, {}, "Child")); +_$insert(_el$19, _$createComponent(Child, {}, "Child", "bindings.jsx:27:5")); const componentChild = _el$19; var _el$20 = _tmpl$3(); -_$spread(_el$20, props, false, undefined, "div"); +_$spread(_el$20, props, false, undefined, "div", "bindings.jsx:32:21"); // Spreads → the tag rides as spread's trailing argument. const spread = _el$20; var _el$21 = _tmpl$7(); -_$spread(_el$21, props, true, undefined, "section"); -_$insert(_el$21, children, undefined, undefined, { name: "section.children" }); +_$spread(_el$21, props, true, undefined, "section", "bindings.jsx:33:37"); +_$insert(_el$21, children, undefined, undefined, { + name: "section.children", + loc: "bindings.jsx:33:48" +}); const spreadWithChildren = _el$21; var _el$22 = _tmpl$8(); _$spread(_el$22, [ @@ -113,7 +149,7 @@ _$spread(_el$22, [ } }, rest, { title: "static" } -], false, undefined, "a"); +], false, undefined, "a", "bindings.jsx:34:38"); const spreadMixed = _el$22; var _el$23 = _tmpl$9(); var _o$ = _$getOwner(); @@ -126,6 +162,9 @@ _$effect(() => { e !== _p$?.e && _$setAttribute(_el$23, "href", e); t !== _p$?.t && _$setAttribute(_el$23, "target", t); _$claimElement(_el$23, _o$); -}, { name: "a.href, a.target" }); +}, { + name: "a.href, a.target", + loc: "bindings.jsx:38:6" +}); // A claim target with bindings → its claim is the tail of the named effect. const link = _el$23; diff --git a/packages/compiler/__tests__/fixtures/dom-source-names/components/output.js b/packages/compiler/__tests__/fixtures/dom-source-names/components/output.js index 8acd109510..964d3a7222 100644 --- a/packages/compiler/__tests__/fixtures/dom-source-names/components/output.js +++ b/packages/compiler/__tests__/fixtures/dom-source-names/components/output.js @@ -7,7 +7,7 @@ import { Show as _$Show } from "r-dom"; var _tmpl$ = /* @__PURE__ */ _$template(``); var _tmpl$2 = /* @__PURE__ */ _$template(`
`); import { Child, Ui, Row } from "./components"; -const Component = () => _$createComponent(Child, { name: "John" }, "Child"); +const Component = () => _$createComponent(Child, { name: "John" }, "Child", "components.jsx:3:25"); var _el$ = _tmpl$2(); var _el$3 = _el$.firstChild; var _el$4 = _el$3.nextSibling; @@ -18,36 +18,39 @@ var _el$8 = _el$7.nextSibling; var _el$9 = _el$8.nextSibling; _$insert(_el$, _$createComponent(Child, _$mergeProps({ name: "Jane" }, props, { get children() { var _el$2 = _tmpl$(); - _$insert(_el$2, name, undefined, undefined, { name: "span.children" }); + _$insert(_el$2, name, undefined, undefined, { + name: "span.children", + loc: "components.jsx:8:13" + }); return _el$2; -} }), "Child"), _el$3); -_$insert(_el$, _$createComponent(Ui.Button, { variant: "primary" }, "Ui.Button"), _el$4); +} }), "Child", "components.jsx:7:5"), _el$3); +_$insert(_el$, _$createComponent(Ui.Button, { variant: "primary" }, "Ui.Button", "components.jsx:10:5"), _el$4); _$insert(_el$, _$createComponent(Ui.Layout.Grid, { cols: 2, children: "text" -}, "Ui.Layout.Grid"), _el$5); +}, "Ui.Layout.Grid", "components.jsx:11:5"), _el$5); _$insert(_el$, _$createComponent(_$For, { get each() { return list(); }, - children: (item) => _$createComponent(Row, { item }, "Row") -}, "For"), _el$6); + children: (item) => _$createComponent(Row, { item }, "Row", "components.jsx:12:33") +}, "For", "components.jsx:12:5"), _el$6); _$insert(_el$, _$createComponent(_$Show, { get when() { return visible(); }, get children() { - return _$createComponent(Child, {}, "Child"); + return _$createComponent(Child, {}, "Child", "components.jsx:14:7"); } -}, "Show"), _el$7); -_$insert(_el$, _$createComponent(_self$.Row, {}, "this.Row"), _el$8); -_$insert(_el$, _$createComponent(Comp, { children: () => _$createComponent(Child, {}, "Child") }, "Comp"), _el$9); +}, "Show", "components.jsx:13:5"), _el$7); +_$insert(_el$, _$createComponent(_self$.Row, {}, "this.Row", "components.jsx:16:5"), _el$8); +_$insert(_el$, _$createComponent(Comp, { children: () => _$createComponent(Child, {}, "Child", "components.jsx:17:18") }, "Comp", "components.jsx:17:5"), _el$9); const template = (() => { const _self$ = this; return _el$; })(); class Container { render() { - return _$createComponent(this.Row, {}, "this.Row"); + return _$createComponent(this.Row, {}, "this.Row", "components.jsx:23:12"); } } diff --git a/packages/compiler/__tests__/fixtures/dom-source-names/locations/output.js b/packages/compiler/__tests__/fixtures/dom-source-names/locations/output.js new file mode 100644 index 0000000000..786889b28c --- /dev/null +++ b/packages/compiler/__tests__/fixtures/dom-source-names/locations/output.js @@ -0,0 +1,76 @@ +import { template as _$template } from "r-dom"; +import { insert as _$insert } from "r-dom"; +import { memo as _$memo } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; +import { spread as _$spread } from "r-dom"; +import { setStyleProperty as _$setStyleProperty } from "r-dom"; +import { readShallow as _$readShallow } from "r-dom"; +import { className as _$className } from "r-dom"; +import { effect as _$effect } from "r-dom"; +import { setAttribute as _$setAttribute } from "r-dom"; +var _tmpl$ = /* @__PURE__ */ _$template(`
`); +var _tmpl$2 = /* @__PURE__ */ _$template(`

Count: `); +var _tmpl$3 = /* @__PURE__ */ _$template(`

`); +// `sourceNames.locations`: every label carries where its JSX is written, +// `file:line:col` relative to `root` (here the fixtures directory, resolved +// against the working directory), line and column 1-based. +import { Layout, Card, Row } from "./components"; +// Nested components → each `createComponent` points at its own ` { + return { + e: _$readShallow(cls()), + t: title() + }; +}, ({ e, t }, _p$) => { + _$className(_el$, e, _p$?.e); + t !== _p$?.t && _$setAttribute(_el$, "title", t); +}, { + name: "div.class, div.title", + loc: "locations/code.jsx:17:24" +}); +// An attribute binding → the attribute's start; a merged effect points at +// the first binding it lists. +const attribute = _el$; +var _el$2 = _tmpl$(); +_$effect(() => color(), (_v$) => { + _$setStyleProperty(_el$2, "color", _v$); +}, { + name: "div.style:color", + loc: "locations/code.jsx:18:26" +}); +const styleObject = _el$2; +var _el$3 = _tmpl$2(); +var _el$4 = _el$3.firstChild; +_$insert(_el$3, count, null, undefined, { + name: "p.children", + loc: "locations/code.jsx:21:24" +}); +// A hole → the `{` of the expression. +const hole = _el$3; +var _el$5 = _tmpl$3(); +_$spread(_el$5, props, false, undefined, "section", "locations/code.jsx:24:25"); +// A spread → the `{...` of the first spread attribute. +const spread = _el$5; +var _el$6 = _tmpl$(); +_$insert(_el$6, (() => { + var _c$ = _$memo(() => { + return !!visible(); + }); + return () => { + return _c$() ? _$createComponent(Card, { title: "On" }, "Card", "locations/code.jsx:27:39") : null; + }; +})(), undefined, undefined, { + name: "div.children", + loc: "locations/code.jsx:27:26" +}); +// A component inside a conditional → the conditional's memo keeps the label. +const conditional = _el$6; diff --git a/packages/compiler/__tests__/fixtures/dom-source-names/ssr/output.js b/packages/compiler/__tests__/fixtures/dom-source-names/ssr/output.js index c05fcc2a32..85e807d060 100644 --- a/packages/compiler/__tests__/fixtures/dom-source-names/ssr/output.js +++ b/packages/compiler/__tests__/fixtures/dom-source-names/ssr/output.js @@ -17,33 +17,33 @@ var _tmpl$2 = [ "
" ]; import { Child, Ui, Row } from "./components"; -const Component = () => _$createComponent(Child, { name: "John" }, "Child"); +const Component = () => _$createComponent(Child, { name: "John" }, "Child", "ssr.jsx:3:25"); const template = (() => { var _v$2 = _$escape(_$createComponent(Child, _$mergeProps({ name: "Jane" }, props, { get children() { return _v$ = () => { return _$escape(name()); }, _$ssr(_tmpl$, _v$); - } }), "Child")), _v$3 = _$escape(_$createComponent(Ui.Button, { variant: "primary" }, "Ui.Button")), _v$4 = _$escape(_$createComponent(Ui.Layout.Grid, { + } }), "Child", "ssr.jsx:7:5")), _v$3 = _$escape(_$createComponent(Ui.Button, { variant: "primary" }, "Ui.Button", "ssr.jsx:10:5")), _v$4 = _$escape(_$createComponent(Ui.Layout.Grid, { cols: 2, children: "text" - }, "Ui.Layout.Grid")), _v$5 = _$escape(_$createComponent(_$For, { + }, "Ui.Layout.Grid", "ssr.jsx:11:5")), _v$5 = _$escape(_$createComponent(_$For, { get each() { return list(); }, - children: (item) => _$createComponent(Row, { item }, "Row") - }, "For")), _v$6 = _$escape(_$createComponent(_$Show, { + children: (item) => _$createComponent(Row, { item }, "Row", "ssr.jsx:12:33") + }, "For", "ssr.jsx:12:5")), _v$6 = _$escape(_$createComponent(_$Show, { get when() { return visible(); }, get children() { - return _$createComponent(Child, {}, "Child"); + return _$createComponent(Child, {}, "Child", "ssr.jsx:14:7"); } - }, "Show")), _v$7 = _$escape(_$createComponent(_self$.Row, {}, "this.Row")), _v$8 = _$escape(_$createComponent(Comp, { children: () => _$createComponent(Child, {}, "Child") }, "Comp")); + }, "Show", "ssr.jsx:13:5")), _v$7 = _$escape(_$createComponent(_self$.Row, {}, "this.Row", "ssr.jsx:16:5")), _v$8 = _$escape(_$createComponent(Comp, { children: () => _$createComponent(Child, {}, "Child", "ssr.jsx:17:18") }, "Comp", "ssr.jsx:17:5")); const _self$ = this; return _$ssr(_tmpl$2, _v$2, _v$3, _v$4, _v$5, _v$6, _v$7, _v$8); })(); class Container { render() { - return _$createComponent(this.Row, {}, "this.Row"); + return _$createComponent(this.Row, {}, "this.Row", "ssr.jsx:23:12"); } } diff --git a/packages/compiler/__tests__/option-matrix.test.js b/packages/compiler/__tests__/option-matrix.test.js index e5e82a9dff..84eeafe2e3 100644 --- a/packages/compiler/__tests__/option-matrix.test.js +++ b/packages/compiler/__tests__/option-matrix.test.js @@ -49,7 +49,7 @@ describe("option-matrix parity", () => { const source = readFixtureSource(modeName, fixture); let babelRaw, oxcRaw, babelErr, oxcErr; try { - babelRaw = compileBabel(source, options); + babelRaw = compileBabel(source, options, `${fixture}.jsx`); } catch (err) { babelErr = err.message.split("\n")[0]; } diff --git a/packages/compiler/__tests__/parity-probes.test.js b/packages/compiler/__tests__/parity-probes.test.js index 5fd816e71a..4f48d3d5fa 100644 --- a/packages/compiler/__tests__/parity-probes.test.js +++ b/packages/compiler/__tests__/parity-probes.test.js @@ -1320,7 +1320,7 @@ describe("Babel vs Oxc parity probes", () => { // nesting in dynamic mode); both compilers rejecting is parity too. let babelOut, babelError; try { - babelOut = normalize(compileBabel(source, options)); + babelOut = normalize(compileBabel(source, options, "probe.jsx")); } catch (err) { babelError = err; } diff --git a/packages/compiler/__tests__/parity/harness.js b/packages/compiler/__tests__/parity/harness.js index 956082a295..7937e6cd4e 100644 --- a/packages/compiler/__tests__/parity/harness.js +++ b/packages/compiler/__tests__/parity/harness.js @@ -524,10 +524,11 @@ function compareFixture(mode, fixture) { const source = readFixtureSource(mode, fixture); let babelRaw, oxcRaw; try { - // Only TSRX modes set `extension`; JSX modes keep Babel filename-free so - // established expectations (e.g. dev-mode location info) stay untouched. - const babelFilename = modes[mode].extension ? `${fixture}${extension}` : undefined; - babelRaw = compileBabel(source, options, babelFilename); + // Both compilers see the same filename: TSRX modes route on the + // extension, and `sourceNames.locations` (on under `dev`) prints the + // cwd-relative path into the output, so a filename on one side only + // would be a divergence of the harness, not the compilers. + babelRaw = compileBabel(source, options, `${fixture}${extension}`); } catch (err) { throw new Error(`babel-plugin failed on ${mode}/${fixture}: ${err.message}`); } diff --git a/packages/compiler/__tests__/reported-parity.test.js b/packages/compiler/__tests__/reported-parity.test.js index 757fc18cd6..d8c5795399 100644 --- a/packages/compiler/__tests__/reported-parity.test.js +++ b/packages/compiler/__tests__/reported-parity.test.js @@ -65,7 +65,7 @@ const view =