diff --git a/cmd/astro-wasm/astro-wasm.go b/cmd/astro-wasm/astro-wasm.go index 3dfdba83..bcaef443 100644 --- a/cmd/astro-wasm/astro-wasm.go +++ b/cmd/astro-wasm/astro-wasm.go @@ -143,21 +143,27 @@ func makeTransformOptions(options js.Value) transform.TransformOptions { experimentalScriptOrder = true } + experimentalExactParsingThingy := false + if jsBool(options.Get("experimentalExactParsingThingy")) { + experimentalExactParsingThingy = true + } + return transform.TransformOptions{ - Filename: filename, - NormalizedFilename: normalizedFilename, - InternalURL: internalURL, - SourceMap: sourcemap, - AstroGlobalArgs: astroGlobalArgs, - Compact: compact, - ResolvePath: resolvePathFn, - PreprocessStyle: preprocessStyle, - ResultScopedSlot: scopedSlot, - ScopedStyleStrategy: scopedStyleStrategy, - TransitionsAnimationURL: transitionsAnimationURL, - AnnotateSourceFile: annotateSourceFile, - RenderScript: renderScript, - ExperimentalScriptOrder: experimentalScriptOrder, + Filename: filename, + NormalizedFilename: normalizedFilename, + InternalURL: internalURL, + SourceMap: sourcemap, + AstroGlobalArgs: astroGlobalArgs, + Compact: compact, + ResolvePath: resolvePathFn, + PreprocessStyle: preprocessStyle, + ResultScopedSlot: scopedSlot, + ScopedStyleStrategy: scopedStyleStrategy, + TransitionsAnimationURL: transitionsAnimationURL, + AnnotateSourceFile: annotateSourceFile, + RenderScript: renderScript, + ExperimentalScriptOrder: experimentalScriptOrder, + ExperimentalExactParsingThingy: experimentalExactParsingThingy, } } @@ -257,7 +263,7 @@ func Parse() any { h := handler.NewHandler(source, parseOptions.Filename) var doc *astro.Node - doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h), astro.ParseOptionEnableLiteral(true)) + doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h), astro.ParseOptionEnableLiteral(true), astro.ParseOptionExperimentalBetterLiteralThingy(transformOptions.ExperimentalExactParsingThingy)) if err != nil { h.AppendError(err) } @@ -281,7 +287,7 @@ func ConvertToTSX() any { h := handler.NewHandler(source, transformOptions.Filename) var doc *astro.Node - doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h), astro.ParseOptionEnableLiteral(true)) + doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h), astro.ParseOptionEnableLiteral(true), astro.ParseOptionExperimentalBetterLiteralThingy(transformOptions.ExperimentalExactParsingThingy)) if err != nil { h.AppendError(err) } @@ -335,7 +341,7 @@ func Transform() any { } }() - doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h)) + doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h), astro.ParseOptionExperimentalBetterLiteralThingy(transformOptions.ExperimentalExactParsingThingy)) if err != nil { reject.Invoke(wasm_utils.ErrorToJSError(h, err)) return diff --git a/internal/doctype.go b/internal/doctype.go index 934abf91..faba7e0b 100644 --- a/internal/doctype.go +++ b/internal/doctype.go @@ -154,3 +154,53 @@ var quirkyIDs = []string{ "-//webtechs//dtd mozilla html 2.0//", "-//webtechs//dtd mozilla html//", } + +func parseDoctypeExact(s string) (n *Node) { + n = &Node{Type: DoctypeNode} + + // Find the name. + space := strings.IndexAny(s, whitespace) + if space == -1 { + space = len(s) + } + n.Data = s[:space] + n.Data = strings.ToLower(n.Data) + s = strings.TrimLeft(s[space:], whitespace) + + if len(s) < 6 { + // It can't start with "PUBLIC" or "SYSTEM". + // Ignore the rest of the string. + return n + } + + key := strings.ToLower(s[:6]) + s = s[6:] + for key == "public" || key == "system" { + s = strings.TrimLeft(s, whitespace) + if s == "" { + break + } + quote := s[0] + if quote != '"' && quote != '\'' { + break + } + s = s[1:] + q := strings.IndexRune(s, rune(quote)) + var id string + if q == -1 { + id = s + s = "" + } else { + id = s[:q] + s = s[q+1:] + } + n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) + if key == "public" { + key = "system" + } else { + key = "" + } + } + + return n +} diff --git a/internal/parser.go b/internal/parser.go index 42d7eb0d..352aff16 100644 --- a/internal/parser.go +++ b/internal/parser.go @@ -393,7 +393,6 @@ func (p *parser) addExpression() { Loc: p.generateLoc(), Namespace: p.top().Namespace, }) - } func isFragment(data string) bool { @@ -725,6 +724,38 @@ func beforeHTMLIM(p *parser) bool { return false } +func initialIMExact(p *parser) bool { + switch p.tok.Type { + case FrontmatterFenceToken: + p.setOriginalIM() + p.im = frontmatterIM + return false + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + Loc: p.generateLoc(), + }) + return true + case DoctypeToken: + n := parseDoctypeExact(p.tok.Data) + p.doc.AppendChild(n) + p.im = inLiteralIMExact + return true + } + if p.frontmatterState == FrontmatterInitial { + p.addFrontmatter(true) + } + p.im = inLiteralIMExact + return false +} + // Section 12.2.6.4.3. func beforeHeadIM(p *parser) bool { switch p.tok.Type { @@ -1776,6 +1807,14 @@ func textIM(p *parser) bool { return p.tok.Type == EndTagToken } +func parseText(p *parser) { + d := p.tok.Data + if d == "" { + return + } + p.addText(d) +} + // Section 12.2.6.4.9. func inTableIM(p *parser) bool { switch p.tok.Type { @@ -2805,6 +2844,42 @@ func inExpressionIM(p *parser) bool { return p.tok.Type == EndTagToken } +func inLiteralIMExact(p *parser) bool { + switch p.tok.Type { + case DoctypeToken: + n := parseDoctypeExact(p.tok.Data) + p.doc.AppendChild(n) + case StartTagToken: + p.addElement() + if p.hasSelfClosingToken { + p.addLoc() + p.oe.pop() + p.acknowledgeSelfClosingTag() + } + case StartExpressionToken: + p.addExpression() + p.setOriginalIM() + p.im = inExpressionIMExact + case ErrorToken: + // ignore the token + case TextToken: + parseText(p) + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + Loc: p.generateLoc(), + }) + case EndTagToken: + p.addLoc() + p.oe.pop() + case EndExpressionToken: + p.addLoc() + p.oe.pop() + } + return true +} + func ignoreTheRemainingTokens(p *parser) bool { return true } @@ -2817,6 +2892,34 @@ func getExitLiteralFunc(p *parser) func() bool { } } +func inExpressionIMExact(p *parser) bool { + switch p.tok.Type { + case ErrorToken: + p.oe.pop() + case TextToken: + parseText(p) + return true + case StartTagToken, EndTagToken: + return inLiteralIMExact(p) + case EndExpressionToken: + p.addLoc() + p.oe.pop() + p.im = p.originalIM + p.originalIM = nil + return true + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + Loc: p.generateLoc(), + }) + return true + } + p.im = p.originalIM + p.originalIM = nil + return p.tok.Type == EndTagToken +} + const whitespaceOrNUL = whitespace + "\x00" // Section 12.2.6.5 @@ -3052,6 +3155,14 @@ func ParseOptionEnableLiteral(enable bool) ParseOption { } } +func ParseOptionExperimentalBetterLiteralThingy(enable bool) ParseOption { + return func(p *parser) { + if enable { + p.im = initialIMExact + } + } +} + // ParseWithOptions is like Parse, with options. func ParseWithOptions(r io.Reader, opts ...ParseOption) (*Node, error) { p := &parser{ diff --git a/internal/printer/__printer_js__/Preserve_namespaces_in_expressions_-_with_exact_parsing.snap b/internal/printer/__printer_js__/Preserve_namespaces_in_expressions_-_with_exact_parsing.snap new file mode 100755 index 00000000..3205c24f --- /dev/null +++ b/internal/printer/__printer_js__/Preserve_namespaces_in_expressions_-_with_exact_parsing.snap @@ -0,0 +1,41 @@ + +[TestPrinter/Preserve_namespaces_in_expressions_-_with_exact_parsing - 1] +## Input + +``` + +``` + +## Output + +```js +import { + Fragment, + render as $$render, + createAstro as $$createAstro, + createComponent as $$createComponent, + renderComponent as $$renderComponent, + renderHead as $$renderHead, + maybeRenderHead as $$maybeRenderHead, + unescapeHTML as $$unescapeHTML, + renderSlot as $$renderSlot, + mergeSlots as $$mergeSlots, + addAttribute as $$addAttribute, + spreadAttributes as $$spreadAttributes, + defineStyleVars as $$defineStyleVars, + defineScriptVars as $$defineScriptVars, + renderTransition as $$renderTransition, + createTransitionScope as $$createTransitionScope, + renderScript as $$renderScript, + createMetadata as $$createMetadata +} from "http://localhost:3000/"; + +export const $$metadata = $$createMetadata(import.meta.url, { modules: [], hydratedComponents: [], clientOnlyComponents: [], hydrationDirectives: new Set([]), hoisted: [] }); + +const $$Component = $$createComponent(($$result, $$props, $$slots) => { + +return $$render`${$$maybeRenderHead($$result)}`; +}, undefined, undefined); +export default $$Component; +``` +--- diff --git a/internal/printer/__printer_js__/React_framework_example_-_with_exact_parsing.snap b/internal/printer/__printer_js__/React_framework_example_-_with_exact_parsing.snap new file mode 100755 index 00000000..39fe3881 --- /dev/null +++ b/internal/printer/__printer_js__/React_framework_example_-_with_exact_parsing.snap @@ -0,0 +1,116 @@ + +[TestPrinter/React_framework_example_-_with_exact_parsing - 1] +## Input + +``` +/-/-/-/ +// Component Imports +import Counter from '../components/Counter.jsx' +const someProps = { + count: 0, +} + +// Full Astro Component Syntax: +// https://docs.astro.build/core-concepts/astro-components/ +/-/-/-/ + + + + + + + + +
+ +

Hello React!

+
+
+ + +``` + +## Output + +```js +import { + Fragment, + render as $$render, + createAstro as $$createAstro, + createComponent as $$createComponent, + renderComponent as $$renderComponent, + renderHead as $$renderHead, + maybeRenderHead as $$maybeRenderHead, + unescapeHTML as $$unescapeHTML, + renderSlot as $$renderSlot, + mergeSlots as $$mergeSlots, + addAttribute as $$addAttribute, + spreadAttributes as $$spreadAttributes, + defineStyleVars as $$defineStyleVars, + defineScriptVars as $$defineScriptVars, + renderTransition as $$renderTransition, + createTransitionScope as $$createTransitionScope, + renderScript as $$renderScript, + createMetadata as $$createMetadata +} from "http://localhost:3000/"; +import Counter from '../components/Counter.jsx' + + +import * as $$module1 from '../components/Counter.jsx'; + +export const $$metadata = $$createMetadata(import.meta.url, { modules: [{ module: $$module1, specifier: '../components/Counter.jsx', assert: {} }], hydratedComponents: [Counter], clientOnlyComponents: [], hydrationDirectives: new Set(['visible']), hoisted: [] }); + +const $$Astro = $$createAstro('https://astro.build'); +const Astro = $$Astro; +const $$Component = $$createComponent(($$result, $$props, $$slots) => { +const Astro = $$result.createAstro($$Astro, $$props, $$slots); +Astro.self = $$Component; + +// Component Imports + +const someProps = { + count: 0, +} + +// Full Astro Component Syntax: +// https://docs.astro.build/core-concepts/astro-components/ + +return $$render` + + + + + + ${$$renderHead($$result)} + +
+ ${$$renderComponent($$result,'Counter',Counter,{...(someProps),"client:visible":true,"client:component-hydration":"visible","client:component-path":("../components/Counter.jsx"),"client:component-export":("default"),"class":"astro-hmnnhvcq"},{"default": () => $$render` +

Hello React!

+ `,})} +
+ +`; +}, undefined, undefined); +export default $$Component; +``` +--- diff --git a/internal/printer/__printer_js__/Self-closing_components_in_head_can_have_siblings.snap b/internal/printer/__printer_js__/Self-closing_components_in_head_can_have_siblings.snap index e11dfd4e..1624a479 100755 --- a/internal/printer/__printer_js__/Self-closing_components_in_head_can_have_siblings.snap +++ b/internal/printer/__printer_js__/Self-closing_components_in_head_can_have_siblings.snap @@ -3,7 +3,7 @@ ## Input ``` - + ``` ## Output diff --git a/internal/printer/__printer_js__/Self-closing_script_in_head_works.snap b/internal/printer/__printer_js__/Self-closing_script_in_head_works.snap index ec156d5e..bb6a9de3 100755 --- a/internal/printer/__printer_js__/Self-closing_script_in_head_works.snap +++ b/internal/printer/__printer_js__/Self-closing_script_in_head_works.snap @@ -3,7 +3,7 @@ ## Input ``` - + + +``` + +## Output + +```js +import { + Fragment, + render as $$render, + createAstro as $$createAstro, + createComponent as $$createComponent, + renderComponent as $$renderComponent, + renderHead as $$renderHead, + maybeRenderHead as $$maybeRenderHead, + unescapeHTML as $$unescapeHTML, + renderSlot as $$renderSlot, + mergeSlots as $$mergeSlots, + addAttribute as $$addAttribute, + spreadAttributes as $$spreadAttributes, + defineStyleVars as $$defineStyleVars, + defineScriptVars as $$defineScriptVars, + renderTransition as $$renderTransition, + createTransitionScope as $$createTransitionScope, + renderScript as $$renderScript, + createMetadata as $$createMetadata +} from "http://localhost:3000/"; + +export const $$metadata = $$createMetadata(import.meta.url, { modules: [], hydratedComponents: [], clientOnlyComponents: [], hydrationDirectives: new Set([]), hoisted: [] }); + +const $$Component = $$createComponent(($$result, $$props, $$slots) => { + +return $$render` + + + + + + + A Basic HTML5 Template + + + + + + + + + + + + + + + +${$$renderHead($$result)} + + + + + +`; +}, undefined, undefined); +export default $$Component; +``` +--- diff --git a/internal/printer/__printer_js__/import.meta.env_-_with_exact_parsing.snap b/internal/printer/__printer_js__/import.meta.env_-_with_exact_parsing.snap new file mode 100755 index 00000000..97f0026f --- /dev/null +++ b/internal/printer/__printer_js__/import.meta.env_-_with_exact_parsing.snap @@ -0,0 +1,131 @@ + +[TestPrinter/import.meta.env_-_with_exact_parsing - 1] +## Input + +``` +/-/-/-/ +import Header from '../../components/Header.jsx' +import Footer from '../../components/Footer.astro' +import ProductPageContent from '../../components/ProductPageContent.jsx'; + +export async function getStaticPaths() { + let products = await fetch(`${import.meta.env.PUBLIC_NETLIFY_URL}/.netlify/functions/get-product-list`) + .then(res => res.json()).then((response) => { + console.log('--- built product pages ---') + return response.products.edges + }); + + return products.map((p, i) => { + return { + params: {pid: p.node.handle}, + props: {product: p}, + }; + }); +} + +const { product } = Astro.props; +/-/-/-/ + + + + + + + Shoperoni | Buy {product.node.title} + + + + + +
+
+
+ +
+
+