From 3c7086a871e903ab11626b99f8ac5bd3167c9907 Mon Sep 17 00:00:00 2001 From: Hans Pagel Date: Mon, 11 Dec 2023 14:55:41 +0100 Subject: [PATCH] refactor: pass target and tree, use acorn and astring for JS --- README.md | 10 ++- packages/snippetz/package.json | 7 +- packages/snippetz/src/format.test.ts | 38 ++++++-- packages/snippetz/src/format.ts | 19 ++-- packages/snippetz/src/print.test.ts | 51 ++++++----- packages/snippetz/src/print.ts | 12 ++- packages/snippetz/src/snippetz.test.ts | 5 +- packages/snippetz/src/snippetz.ts | 29 +++--- packages/snippetz/src/undici.test.ts | 39 ++++---- packages/snippetz/src/undici.ts | 118 ++++++++++--------------- pnpm-lock.yaml | 10 ++- 11 files changed, 183 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index f0255bf..970d873 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ [![GitHub License](https://img.shields.io/github/license/scalar/snippetz)](https://github.com/scalar/snippetz/blob/main/LICENSE) [![Discord](https://img.shields.io/discord/1135330207960678410?style=flat&color=5865F2)](https://discord.gg/8HeZcRGPFS) +## Installation + +``` +npm install @scalar/snippetz +``` + ## Quickstart ```js @@ -21,11 +27,11 @@ const snippet = snippetz().get( Output: diff --git a/packages/snippetz/package.json b/packages/snippetz/package.json index 6c99ec5..727f02d 100644 --- a/packages/snippetz/package.json +++ b/packages/snippetz/package.json @@ -14,10 +14,13 @@ "vitest": "^1.0.4" }, "dependencies": { - "abstract-syntax-tree": "^2.21.1", + "acorn": "^8.11.2", + "astring": "^1.8.6", "prettier": "^3.1.1" }, - "files": ["dist"], + "files": [ + "dist" + ], "main": "./dist/index.umd.cjs", "module": "./dist/index.js", "exports": { diff --git a/packages/snippetz/src/format.test.ts b/packages/snippetz/src/format.test.ts index cf9b91e..3699ba9 100644 --- a/packages/snippetz/src/format.test.ts +++ b/packages/snippetz/src/format.test.ts @@ -1,14 +1,34 @@ import { expect, test } from 'vitest' import { format } from './format' -test('format', async () => { - const source = `import {request} from "undici"; -const {statusCode, headers, trailers, body} = await request("http://localhost:3000/foo"); -` +const tree = { + type: 'Program', + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + type: 'VariableDeclarator', + id: { + type: 'Identifier', + name: 'answer', + }, + init: { + type: 'Literal', + value: 42, + }, + }, + ], + kind: 'const', + }, + ], +} - expect(await format(source)).toBe(`import { request } from 'undici' -const { statusCode, headers, trailers, body } = await request( - 'http://localhost:3000/foo', -) -`) +test('format', async () => { + expect( + await format({ + target: 'js', + tree, + }) + ).toBe(`const answer = 42\n`) }) diff --git a/packages/snippetz/src/format.ts b/packages/snippetz/src/format.ts index 4e67e62..733f379 100644 --- a/packages/snippetz/src/format.ts +++ b/packages/snippetz/src/format.ts @@ -1,9 +1,16 @@ import * as prettier from 'prettier' +import { print } from './print' -export async function format(source: string) { - return await prettier.format(source, { - semi: false, - parser: 'babel', - singleQuote: true, - }) +export async function format(source: any) { + const target = source.target + + if (target === 'js') { + return await prettier.format(print(source), { + semi: false, + parser: 'babel', + singleQuote: true, + }) + } + + throw new Error(`Unsupported target: ${source.target}`) } diff --git a/packages/snippetz/src/print.test.ts b/packages/snippetz/src/print.test.ts index 6a475c4..d3e5096 100644 --- a/packages/snippetz/src/print.test.ts +++ b/packages/snippetz/src/print.test.ts @@ -1,29 +1,34 @@ import { expect, test } from 'vitest' import { print } from './print' -test('print', async () => { - const tree = { - type: 'Program', - body: [ - { - type: 'VariableDeclaration', - declarations: [ - { - type: 'VariableDeclarator', - id: { - type: 'Identifier', - name: 'answer', - }, - init: { - type: 'Literal', - value: 42, - }, +const tree = { + type: 'Program', + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + type: 'VariableDeclarator', + id: { + type: 'Identifier', + name: 'answer', + }, + init: { + type: 'Literal', + value: 42, }, - ], - kind: 'const', - }, - ], - } + }, + ], + kind: 'const', + }, + ], +} - expect(await print(tree)).toBe(`const answer = 42;\n`) +test('print', async () => { + expect( + await print({ + target: 'js', + tree, + }) + ).toBe(`const answer = 42;\n`) }) diff --git a/packages/snippetz/src/print.ts b/packages/snippetz/src/print.ts index 615dc3a..c49dcbe 100644 --- a/packages/snippetz/src/print.ts +++ b/packages/snippetz/src/print.ts @@ -1,5 +1,11 @@ -import { generate } from 'abstract-syntax-tree' +import { generate } from 'astring' -export function print(tree: any) { - return generate(tree) +export function print(source: any) { + const target = source.target + + if (target === 'js') { + return generate(source.tree) + } + + throw new Error(`Unsupported target: ${source.target}`) } diff --git a/packages/snippetz/src/snippetz.test.ts b/packages/snippetz/src/snippetz.test.ts index e68c65d..64691bd 100644 --- a/packages/snippetz/src/snippetz.test.ts +++ b/packages/snippetz/src/snippetz.test.ts @@ -25,7 +25,10 @@ const tree = { } test('snippetz', async () => { - const snippet = await snippetz().get(tree) + const snippet = await snippetz().get({ + target: 'js', + tree, + }) expect(snippet).toBe(`const answer = 42\n`) }) diff --git a/packages/snippetz/src/snippetz.ts b/packages/snippetz/src/snippetz.ts index 309b610..5f48bc6 100644 --- a/packages/snippetz/src/snippetz.ts +++ b/packages/snippetz/src/snippetz.ts @@ -1,20 +1,23 @@ import { format } from './format' import { print } from './print' -export type SnippetOptions = {} - -export class Snippetz { - constructor(options?: SnippetOptions) { - // console.log(options) - } +export type SnippetOptions = { + format: boolean +} - get(tree: any) { - return format(print(tree)) - } +const defaultOptions = { + format: true, } -export function snippetz(tree: any) { - return new Snippetz({ - tree, - }) +export function snippetz() { + return { + get(source: any, options?: Partial) { + options = { + ...defaultOptions, + ...options, + } + + return options.format ? format(source) : print(source) + }, + } } diff --git a/packages/snippetz/src/undici.test.ts b/packages/snippetz/src/undici.test.ts index 990fe99..0e5eabf 100644 --- a/packages/snippetz/src/undici.test.ts +++ b/packages/snippetz/src/undici.test.ts @@ -1,27 +1,28 @@ -import { expect, test } from 'vitest' +import { expect, describe, it } from 'vitest' import { undici } from './undici' import { print } from './print' -// import { parse } from 'abstract-syntax-tree' -test('undici', () => { - // const source = `import { request } from 'undici' +describe('undici', () => { + it('basic request', () => { + const tree = undici({ + url: 'https://example.com', + }) - // const { - // statusCode, - // headers, - // trailers, - // body - // } = await request('http://localhost:3000/foo')` - - // const tree = parse(source) - - // console.log(JSON.stringify(tree)) - - const tree = undici({ - url: 'http://localhost:3000/foo', + expect(print(tree)).toBe(`import {request} from "undici"; +const {statusCode, headers, trailers, body} = await request("https://example.com"); +`) }) - expect(print(tree)).toBe(`import {request} from "undici"; -const {statusCode, headers, trailers, body} = await request("http://localhost:3000/foo"); + it('POST request', () => { + const tree = undici({ + url: 'https://example.com', + method: 'post', + }) + + expect(print(tree)).toBe(`import {request} from "undici"; +const {statusCode, headers, trailers, body} = await request("https://example.com", { + "method": "POST" +}); `) + }) }) diff --git a/packages/snippetz/src/undici.ts b/packages/snippetz/src/undici.ts index 927cb2b..2373b44 100644 --- a/packages/snippetz/src/undici.ts +++ b/packages/snippetz/src/undici.ts @@ -1,77 +1,49 @@ +import { Parser } from 'acorn' + export function undici(request: any) { + // Defaults + const normalizedRequest = { + method: 'GET', + ...request, + } + + // Normalization + normalizedRequest.method = normalizedRequest.method.toUpperCase() + + // Reset undici defaults + const options = { + method: + normalizedRequest.method === 'GET' ? undefined : normalizedRequest.method, + } + + // Remove undefined keys + Object.keys(options).forEach((key) => { + if (options[key] === undefined) { + delete options[key] + } + }) + + // Transform to JSON + const jsonOptions = Object.keys(options).length + ? `, ${JSON.stringify(options, null, 2)}` + : '' + + // Code Template + const code = ` + +import { request } from "undici" + +const { statusCode, headers, trailers, body } = await request("${normalizedRequest.url}"${jsonOptions}) + +` + + // Create an AST return { - type: 'Program', - sourceType: 'module', - body: [ - { - type: 'ImportDeclaration', - specifiers: [ - { - type: 'ImportSpecifier', - local: { type: 'Identifier', name: 'request' }, - imported: { type: 'Identifier', name: 'request' }, - }, - ], - source: { type: 'Literal', value: 'undici' }, - }, - { - type: 'VariableDeclaration', - kind: 'const', - declarations: [ - { - type: 'VariableDeclarator', - id: { - type: 'ObjectPattern', - properties: [ - { - type: 'Property', - key: { type: 'Identifier', name: 'statusCode' }, - value: { type: 'Identifier', name: 'statusCode' }, - kind: 'init', - computed: false, - method: false, - shorthand: true, - }, - { - type: 'Property', - key: { type: 'Identifier', name: 'headers' }, - value: { type: 'Identifier', name: 'headers' }, - kind: 'init', - computed: false, - method: false, - shorthand: true, - }, - { - type: 'Property', - key: { type: 'Identifier', name: 'trailers' }, - value: { type: 'Identifier', name: 'trailers' }, - kind: 'init', - computed: false, - method: false, - shorthand: true, - }, - { - type: 'Property', - key: { type: 'Identifier', name: 'body' }, - value: { type: 'Identifier', name: 'body' }, - kind: 'init', - computed: false, - method: false, - shorthand: true, - }, - ], - }, - init: { - type: 'AwaitExpression', - argument: { - type: 'CallExpression', - callee: { type: 'Identifier', name: 'request' }, - arguments: [{ type: 'Literal', value: request.url }], - }, - }, - }, - ], - }, - ], + target: 'js', + tree: Parser.parse(code, { + ecmaVersion: 2022, + locations: false, + sourceType: 'module', + }), } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df3ab5b..0c5a48d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,9 +30,12 @@ importers: packages/snippetz: dependencies: - abstract-syntax-tree: - specifier: ^2.21.1 - version: 2.21.1 + acorn: + specifier: ^8.11.2 + version: 8.11.2 + astring: + specifier: ^1.8.6 + version: 1.8.6 prettier: specifier: ^3.1.1 version: 3.1.1 @@ -428,7 +431,6 @@ packages: resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}