From 02f3510300106a9df9967f562c6fa9583de3f18e Mon Sep 17 00:00:00 2001 From: jonambas Date: Sun, 7 Apr 2024 00:20:09 -0400 Subject: [PATCH] feat(parser): add support for import aliases, fix jsx parsing --- src/__tests__/codegen.test.ts | 7 +-- src/__tests__/fixtures.ts | 8 ++++ src/__tests__/map.test.ts | 8 ++-- src/__tests__/parser.test.ts | 83 +++++++++++++++++++++++++---------- src/parser.ts | 53 +++++++++++++--------- src/types.ts | 2 + 6 files changed, 107 insertions(+), 54 deletions(-) create mode 100644 src/__tests__/fixtures.ts diff --git a/src/__tests__/codegen.test.ts b/src/__tests__/codegen.test.ts index 82727b3..cf44b8b 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', () => { 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..8c1269e 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -1,37 +1,74 @@ 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: `
`, - }, - context, - ); + it('skips without ct in contents', () => { + expect( + makeParser(`
`), + ).toBeUndefined(); + + expect( + makeParser(`import { css } from '@/styled-system/css`), + ).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..9cdd6e0 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,41 @@ 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) ?? []; - - for (const call of calls) { - const path = call - .match(/['"][\w.]+['"]/) - ?.toString() - .replace(/['"]/g, ''); - if (!path) continue; - const value = map.get(path); - text = text.replace( - call, - isObject(value) ? JSON.stringify(value) : `'${value}'`, - ); + let ctExists = false; + let ctReplaced = false; + let ctAlias = 'ct'; + + for (const node of source.getImportDeclarations()) { + if (!node.getText().includes('ct')) continue; + for (const named of node.getNamedImports()) { + if (named.getText() === 'ct') { + ctExists = true; + ctAlias = named.getAliasNode()?.getText() ?? 'ct'; + break; + } + } + } + + if (!ctExists) return; + + for (const node of source.getDescendantsOfKind( + ts.SyntaxKind.CallExpression, + )) { + if (node.getExpression().getText() === ctAlias) { + const path = node.getArguments()[0]?.getText().replace(/['"]/g, ''); + const value = map.get(path); + + node.replaceWithText( + isObject(value) ? JSON.stringify(value) : `'${value}'`, + ); + ctReplaced = true; + } } - return text; + return ctReplaced ? source.getText() : undefined; }; diff --git a/src/types.ts b/src/types.ts index f8e7238..8781808 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,4 @@ +// import { LoggerInterface } from '@pandacss/types'; import { type Project } from 'ts-morph'; export type ComponentTokens = { [k: string]: string | ComponentTokens }; @@ -6,4 +7,5 @@ export type PluginContext = { project: Project; tokens: ComponentTokens; map: Map; + // logger?: LoggerInterface; };