From 76fcef3600eec51092e4dbab7fd6d716605a480e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 11 Aug 2022 16:01:00 -0500 Subject: [PATCH] Better handling for special characters (#496) * fix: better handling for special characters * chore: prettier format Co-authored-by: Nate Moore --- .changeset/stale-starfishes-fry.md | 5 ++ internal/transform/transform.go | 19 +++- .../client-directive/special-characters.ts | 87 +++++++++++++++++++ 3 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 .changeset/stale-starfishes-fry.md create mode 100644 packages/compiler/test/client-directive/special-characters.ts diff --git a/.changeset/stale-starfishes-fry.md b/.changeset/stale-starfishes-fry.md new file mode 100644 index 000000000..a3ec99742 --- /dev/null +++ b/.changeset/stale-starfishes-fry.md @@ -0,0 +1,5 @@ +--- +'@astrojs/compiler': patch +--- + +Better handling for imports which use special characters diff --git a/internal/transform/transform.go b/internal/transform/transform.go index 243ea0959..8ca938eaf 100644 --- a/internal/transform/transform.go +++ b/internal/transform/transform.go @@ -390,16 +390,27 @@ func matchNodeToImportStatement(doc *astro.Node, n *astro.Node) *ImportMatch { return match } +func safeURL(pathname string) string { + // url.PathEscape also escapes `/` to `%2F`, but we don't want that! + escaped := strings.ReplaceAll(url.PathEscape(pathname), "%2F", "/") + return escaped +} + func resolveIdForMatch(match *ImportMatch, opts *TransformOptions) string { if strings.HasPrefix(match.Specifier, ".") && len(opts.Pathname) > 0 { - u, err := url.Parse(opts.Pathname) + pathname := safeURL(opts.Pathname) + u, err := url.Parse(pathname) if err == nil { - ref, _ := url.Parse(match.Specifier) + spec := safeURL(match.Specifier) + ref, _ := url.Parse(spec) ou := u.ResolveReference(ref) - return ou.String() + unescaped, _ := url.PathUnescape(ou.String()) + fmt.Println(unescaped) + return unescaped } } - return "" + // If we can't manipulate the URLs, fallback to the exact specifier + return match.Specifier } func eachImportStatement(doc *astro.Node, cb func(stmt js_scanner.ImportStatement) bool) { diff --git a/packages/compiler/test/client-directive/special-characters.ts b/packages/compiler/test/client-directive/special-characters.ts new file mode 100644 index 000000000..1c6915532 --- /dev/null +++ b/packages/compiler/test/client-directive/special-characters.ts @@ -0,0 +1,87 @@ +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; +import { transform } from '@astrojs/compiler'; + +const FIXTURE = ` +--- +import CaretCounter from '../components/^--with-carets/Counter'; +import RocketCounter from '../components/and-rockets-🚀/Counter'; +import PercentCounter from '../components/now-100%-better/Counter'; +import SpaceCounter from '../components/with some spaces/Counter'; +import RoundBracketCounter from '../components/with-(round-brackets)/Counter'; +import SquareBracketCounter from '../components/with-[square-brackets]/Counter'; +import RemoteComponent from 'https://test.com/components/with-[wacky-brackets}()10%-cooler/Counter'; +--- + + +

Special chars in component import paths from an .astro file

+ + + + + + + + + +`; + +let result; +test.before(async () => { + result = await transform(FIXTURE, { experimentalStaticExtraction: true, pathname: '/@fs/users/astro/apps/pacman/src/pages/index.astro' }); +}); + +test('does not panic', () => { + assert.ok(result.code); +}); + +test('hydrated components with carets', () => { + let components = result.hydratedComponents; + assert.equal(components[0].exportName, 'default'); + assert.equal(components[0].specifier, '../components/^--with-carets/Counter'); + assert.equal(components[0].resolvedPath, '/@fs/users/astro/apps/pacman/src/components/^--with-carets/Counter'); +}); + +test('hydrated components with rockets', () => { + let components = result.hydratedComponents; + assert.equal(components[1].exportName, 'default'); + assert.equal(components[1].specifier, '../components/and-rockets-🚀/Counter'); + assert.equal(components[1].resolvedPath, '/@fs/users/astro/apps/pacman/src/components/and-rockets-🚀/Counter'); +}); + +test('hydrated components with percent', () => { + let components = result.hydratedComponents; + assert.equal(components[2].exportName, 'default'); + assert.equal(components[2].specifier, '../components/now-100%-better/Counter'); + assert.equal(components[2].resolvedPath, '/@fs/users/astro/apps/pacman/src/components/now-100%-better/Counter'); +}); + +test('hydrated components with spaces', () => { + let components = result.hydratedComponents; + assert.equal(components[3].exportName, 'default'); + assert.equal(components[3].specifier, '../components/with some spaces/Counter'); + assert.equal(components[3].resolvedPath, '/@fs/users/astro/apps/pacman/src/components/with some spaces/Counter'); +}); + +test('hydrated components with round brackets', () => { + let components = result.hydratedComponents; + assert.equal(components[4].exportName, 'default'); + assert.equal(components[4].specifier, '../components/with-(round-brackets)/Counter'); + assert.equal(components[4].resolvedPath, '/@fs/users/astro/apps/pacman/src/components/with-(round-brackets)/Counter'); +}); + +test('hydrated components with square brackets', () => { + let components = result.hydratedComponents; + assert.equal(components[5].exportName, 'default'); + assert.equal(components[5].specifier, '../components/with-[square-brackets]/Counter'); + assert.equal(components[5].resolvedPath, '/@fs/users/astro/apps/pacman/src/components/with-[square-brackets]/Counter'); +}); + +test('hydrated components with kitchen-sink', () => { + let components = result.hydratedComponents; + assert.equal(components[6].exportName, 'default'); + assert.equal(components[6].specifier, 'https://test.com/components/with-[wacky-brackets}()10%-cooler/Counter'); + assert.equal(components[6].resolvedPath, 'https://test.com/components/with-[wacky-brackets}()10%-cooler/Counter'); +}); + +test.run();