Skip to content

Commit 9aa32ff

Browse files
committed
Merge remote-tracking branch 'origin/next' into kasper/nx-up
2 parents ee9b699 + da3a563 commit 9aa32ff

File tree

3 files changed

+177
-3
lines changed

3 files changed

+177
-3
lines changed

code/renderers/react/src/componentManifest/getComponentImports.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,114 @@ test('Non-relative import remains unchanged even if packageName provided', () =>
585585
);
586586
});
587587

588+
test('Rewrites tilde-prefixed source to packageName', () => {
589+
const code = dedent`
590+
import { Button } from '~/components/Button';
591+
592+
const meta = {};
593+
export default meta;
594+
export const S = <Button/>;
595+
`;
596+
expect(getImports(code, 'pkg')).toMatchInlineSnapshot(
597+
`
598+
{
599+
"components": [
600+
{
601+
"componentName": "Button",
602+
"importId": "~/components/Button",
603+
"importName": "Button",
604+
"localImportName": "Button",
605+
},
606+
],
607+
"imports": [
608+
"import { Button } from \"pkg\";",
609+
],
610+
}
611+
`
612+
);
613+
});
614+
615+
test('Rewrites hash-prefixed source to packageName', () => {
616+
const code = dedent`
617+
import Btn from '#Button';
618+
619+
const meta = {};
620+
export default meta;
621+
export const S = <Btn/>;
622+
`;
623+
expect(getImports(code, 'my-package')).toMatchInlineSnapshot(
624+
`
625+
{
626+
"components": [
627+
{
628+
"componentName": "Btn",
629+
"importId": "#Button",
630+
"importName": "default",
631+
"localImportName": "Btn",
632+
},
633+
],
634+
"imports": [
635+
"import { Btn } from "my-package";",
636+
],
637+
}
638+
`
639+
);
640+
});
641+
642+
test('Does not rewrite scoped package subpath (valid bare specifier)', () => {
643+
const code = dedent`
644+
import { Button } from '@scope/ui/components';
645+
646+
const meta = {};
647+
export default meta;
648+
export const S = <Button/>;
649+
`;
650+
expect(getImports(code, 'pkg')).toMatchInlineSnapshot(
651+
`
652+
{
653+
"components": [
654+
{
655+
"componentName": "Button",
656+
"importId": "@scope/ui/components",
657+
"importName": "Button",
658+
"localImportName": "Button",
659+
},
660+
],
661+
"imports": [
662+
"import { Button } from \"@scope/ui/components\";",
663+
],
664+
}
665+
`
666+
);
667+
});
668+
669+
test('Does not rewrite unscoped package subpath (valid bare specifier)', () => {
670+
const code = dedent`
671+
import { Button } from 'ui/components';
672+
673+
const meta = {};
674+
export default meta;
675+
export const S = <Button/>;
676+
`;
677+
expect(getImports(code, 'pkg')).toMatchInlineSnapshot(
678+
`
679+
{
680+
"components": [
681+
{
682+
"componentName": "Button",
683+
"importId": "ui/components",
684+
"importName": "Button",
685+
"localImportName": "Button",
686+
},
687+
],
688+
"imports": [
689+
"import { Button } from \"ui/components\";",
690+
],
691+
}
692+
`
693+
);
694+
});
695+
588696
// Merging imports from same package
589697

590698
test('Merges multiple imports from the same package (defaults and named)', () => {

code/renderers/react/src/componentManifest/getComponentImports.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { logger } from 'storybook/internal/node-logger';
66

77
import { getImportTag, getReactDocgen, matchPath } from './reactDocgen';
88
import { cachedResolveImport } from './utils';
9+
import { stripSubpath, validPackageName } from './valid-package-name';
910

1011
// Public component metadata type used across passes
1112
export type ComponentRef = {
@@ -256,8 +257,6 @@ export const getImports = ({
256257
order: number;
257258
};
258259

259-
const isRelative = (id: string) => id.startsWith('.') || id === '.';
260-
261260
const withSource = components
262261
.filter((c) => Boolean(c.importId))
263262
.map((c, idx) => {
@@ -281,7 +280,7 @@ export const getImports = ({
281280
const rewritten =
282281
overrideSource !== undefined
283282
? overrideSource
284-
: packageName && isRelative(importId)
283+
: packageName && !validPackageName(stripSubpath(importId))
285284
? packageName
286285
: importId;
287286
return { c, src: t.stringLiteral(rewritten), key: rewritten, ord: idx };
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// inspired by https://github.com/npm/validate-npm-package-name/blob/main/lib/index.js
2+
const scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$');
3+
4+
export function stripSubpath(name: string): string {
5+
const parts = name.split('/');
6+
7+
if (name.startsWith('@')) {
8+
// @scope/pkg/...
9+
if (parts.length >= 3) {
10+
return `${parts[0]}/${parts[1]}`;
11+
}
12+
return name;
13+
}
14+
15+
// react/..., lodash/..., etc
16+
return parts[0];
17+
}
18+
19+
export function validPackageName(name: string) {
20+
if (!name.length) {
21+
return false;
22+
}
23+
24+
if (name.startsWith('.')) {
25+
return false;
26+
}
27+
28+
if (name.match(/^_/)) {
29+
return false;
30+
}
31+
32+
if (name.trim() !== name) {
33+
return false;
34+
}
35+
36+
if (name.length > 214) {
37+
return false;
38+
}
39+
40+
// mIxeD CaSe nAMEs
41+
if (name.toLowerCase() !== name) {
42+
return false;
43+
}
44+
45+
if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
46+
return false;
47+
}
48+
49+
if (encodeURIComponent(name) !== name) {
50+
const nameMatch = name.match(scopedPackagePattern);
51+
if (nameMatch) {
52+
const org = nameMatch[1];
53+
const pkg = nameMatch[2];
54+
55+
if (pkg.startsWith('.')) {
56+
return false;
57+
}
58+
59+
if (encodeURIComponent(org) === org && encodeURIComponent(pkg) === pkg) {
60+
return true;
61+
}
62+
}
63+
return false;
64+
}
65+
66+
return true;
67+
}

0 commit comments

Comments
 (0)