diff --git a/src/__tests__/codegen.test.ts b/src/__tests__/codegen.test.ts index 82727b3..58368db 100644 --- a/src/__tests__/codegen.test.ts +++ b/src/__tests__/codegen.test.ts @@ -1,10 +1,5 @@ import { codegen } from '../codegen'; -import { createContext } from '../context'; - -const context = createContext({ - foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, - bar: { 100: 'red', 200: 'blue' }, -}); +import { context } from './fixtures'; describe('codegen', () => { it('generates ct runtime code', () => { @@ -33,7 +28,7 @@ describe('codegen', () => { "files": [ { "code": " - const pluginCtMap = new Map([["foo.100","#fff"],["foo.200",{"base":"#000"}],["bar.100","red"],["bar.200","blue"]]); + const pluginCtMap = new Map([["foo.100","#fff"],["foo.200",{"base":"#000","lg":"#111"}],["bar.100","red"],["bar.200","blue"]]); export const ct = (path) => { if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found'; diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts new file mode 100644 index 0000000..0f6615a --- /dev/null +++ b/src/__tests__/fixtures.ts @@ -0,0 +1,8 @@ +import { createContext } from '../context'; + +export const tokens = { + foo: { 100: { value: '#fff' }, 200: { value: { base: '#000', lg: '#111' } } }, + bar: { 100: 'red', 200: 'blue' }, +}; + +export const context = createContext(tokens); diff --git a/src/__tests__/map.test.ts b/src/__tests__/map.test.ts index 16ca7ae..d0d942d 100644 --- a/src/__tests__/map.test.ts +++ b/src/__tests__/map.test.ts @@ -1,9 +1,5 @@ import { get, makeMap, makePaths, mapTemplate } from '../map'; - -const tokens = { - foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, - bar: { 100: 'red', 200: 'blue' }, -}; +import { tokens } from './fixtures'; describe('get', () => { it('gets a string', () => { @@ -15,6 +11,7 @@ describe('get', () => { ` { "base": "#000", + "lg": "#111", } `, ); @@ -67,6 +64,7 @@ describe('makeMap', () => { "foo.100" => "#fff", "foo.200" => { "base": "#000", + "lg": "#111", }, "bar.100" => "red", "bar.200" => "blue", diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts index cf148d4..2311d65 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -1,37 +1,113 @@ import { parser } from '../parser'; -import { createContext } from '../context'; +import { context } from './fixtures'; -const context = createContext({ - foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, - bar: { 100: 'red', 200: 'blue' }, -}); +export const makeParser = (content: string) => { + return parser( + { + configure: () => {}, + filePath: 'test.tsx', + content, + }, + context, + ); +}; describe('parser', () => { it('parses', () => { - const res = parser( - { - configure: () => {}, - filePath: 'test.tsx', - content: `
`, + const res = makeParser(` + import { css, ct, cva } from '@/styled-system/css'; + + const styles = cva({ + base: { + // background: ct('foo.200'), + color: ct('bar.200'), }, - context, - ); + }); + + export const Component = () => { + return (
); + `); expect(res).toMatchInlineSnapshot( - `"
"`, + ` + "import { css, ct, cva } from '@/styled-system/css'; + + const styles = cva({ + base: { + // background: ct('foo.200'), + color: 'blue', + }, + }); + + export const Component = () => { + return (
); + " + `, ); }); - it('skips without "ct(" in contents', () => { - const res = parser( - { - configure: () => {}, - filePath: 'test.tsx', - content: `
`, + it('parses with an alias', () => { + const res = makeParser(` + import { css, ct as alias, cva } from '@/styled-system/css'; + + const styles = cva({ + base: { + // background: ct('foo.200'), + color: ct('bar.200'), }, - context, - ); + }); + + export const Component = () => { + return (
); + `); + + expect(res).toMatchInlineSnapshot(` + "import { css, ct as alias, cva } from '@/styled-system/css'; + + const styles = cva({ + base: { + // background: ct('foo.200'), + color: ct('bar.200'), + }, + }); + + export const Component = () => { + return (
); + " + `); + }); + + it('skips without ct imports or expressions', () => { + expect( + makeParser(`
`), + ).toBeUndefined(); - expect(res).toBeUndefined(); + expect( + makeParser(`import { ct } from '@/styled-system/css`), + ).toBeUndefined(); }); }); diff --git a/src/parser.ts b/src/parser.ts index a04e283..1f573e9 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,6 +1,7 @@ import type { ParserResultBeforeHookArgs } from '@pandacss/types'; import type { PluginContext } from './types'; import { isObject } from './utils'; +import { ts } from 'ts-morph'; export const parser = ( args: ParserResultBeforeHookArgs, @@ -8,29 +9,39 @@ export const parser = ( ): string | void => { const { project, map } = context; - // TODO: handle `import { ct as xyz }` aliasing - const content = args.content; - if (!content.includes('ct(')) return; - - const source = project.createSourceFile('__temp-ct-parser.ts', content, { + // Note: parser won't replace `ct` calls in JSX without .tsx + const source = project.createSourceFile('__ct-parser.tsx', args.content, { overwrite: true, }); - let text = source.getText(); - const calls = text.match(/ct\(['"][\w.]+['"]\)/g) ?? []; + let exists = false; + let alias = 'ct'; + + for (const node of source.getImportDeclarations()) { + if (!node.getText().includes('ct')) continue; + for (const named of node.getNamedImports()) { + if (named.getText() === 'ct' || named.getText().startsWith('ct as')) { + exists = true; + alias = named.getAliasNode()?.getText() ?? 'ct'; + break; + } + } + } + + if (!exists) return; + + const calls = source + .getDescendantsOfKind(ts.SyntaxKind.CallExpression) + .filter((node) => node.getExpression().getText() === alias); - for (const call of calls) { - const path = call - .match(/['"][\w.]+['"]/) - ?.toString() - .replace(/['"]/g, ''); - if (!path) continue; + for (const node of calls) { + const path = node.getArguments()[0]?.getText().replace(/['"]/g, ''); const value = map.get(path); - text = text.replace( - call, + + node.replaceWithText( isObject(value) ? JSON.stringify(value) : `'${value}'`, ); } - return text; + return calls.length ? source.getText() : undefined; };