diff --git a/package.json b/package.json index d2872bf..64888d8 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "@types/node": "^20.2.5", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", - "astring": "^1.8.5", + "@vardario/astring-ts-generator": "^1.1.0", + "astring": "^1.9.0", "eslint": "^8.54.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-unused-imports": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1648fe2..9c8d047 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,9 +30,12 @@ importers: '@typescript-eslint/parser': specifier: ^6.11.0 version: 6.12.0(eslint@8.54.0)(typescript@5.0.4) + '@vardario/astring-ts-generator': + specifier: ^1.1.0 + version: 1.1.0(astring@1.9.0) astring: - specifier: ^1.8.5 - version: 1.8.5 + specifier: ^1.9.0 + version: 1.9.0 eslint: specifier: ^8.54.0 version: 8.54.0 @@ -574,6 +577,11 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@vardario/astring-ts-generator@1.1.0': + resolution: {integrity: sha512-gLIXskiw5/79UjvXL8BC7ORYJ/5xYKwys2e248bhrm06QzEXnNSTuEaDW3ZxfYN+04nAuAbONdbHXNeD9GCGbg==} + peerDependencies: + astring: ^1.9.0 + '@vitest/expect@2.1.3': resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} @@ -695,8 +703,8 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - astring@1.8.5: - resolution: {integrity: sha512-TuBbdn7jWVzf8dmFGTaRpW8qgANtWLi1qJLnkfGO5uVf6jf9f/F4B1H35tnOI+qVYZo3p3i8WZlbZOuPAE0wEA==} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true author-regex@1.0.0: @@ -2668,6 +2676,10 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vardario/astring-ts-generator@1.1.0(astring@1.9.0)': + dependencies: + astring: 1.9.0 + '@vitest/expect@2.1.3': dependencies: '@vitest/spy': 2.1.3 @@ -2784,7 +2796,7 @@ snapshots: assertion-error@2.0.1: {} - astring@1.8.5: {} + astring@1.9.0: {} author-regex@1.0.0: {} diff --git a/src/print-html.ts b/src/print-html.ts index 129e02b..3510b20 100644 --- a/src/print-html.ts +++ b/src/print-html.ts @@ -3,49 +3,9 @@ import _ from 'lodash'; import { walk } from 'estree-walker'; import { AST } from 'svelte/compiler'; import { DefaultPrinterIdentOptions, PrinterIdentOptions } from './index.js'; -import { Node } from 'estree'; -export type Write = (text: string) => void; +import { ElementLike, printAttributes, SvelteNode } from './utils.js'; -type ElementLike = - | AST.Component - | AST.TitleElement - | AST.SlotElement - | AST.RegularElement - | AST.SvelteBody - | AST.SvelteComponent - | AST.SvelteDocument - | AST.SvelteElement - | AST.SvelteFragment - | AST.SvelteHead - | AST.SvelteOptionsRaw - | AST.SvelteSelf - | AST.SvelteWindow; - -type Directive = - | AST.AnimateDirective - | AST.BindDirective - | AST.ClassDirective - | AST.LetDirective - | AST.OnDirective - | AST.StyleDirective - | AST.TransitionDirective - | AST.UseDirective; - -type Tag = AST.ExpressionTag | AST.HtmlTag | AST.ConstTag | AST.DebugTag | AST.RenderTag; -type Block = AST.EachBlock | AST.IfBlock | AST.AwaitBlock | AST.KeyBlock | AST.SnippetBlock; - -type TemplateNode = - | AST.Root - | AST.Text - | Tag - | ElementLike - | AST.Attribute - | AST.SpreadAttribute - | Directive - | AST.Comment - | Block; - -type SvelteNode = Node | TemplateNode | AST.Fragment | any; +export type Write = (text: string) => void; const HTML_VOID_ELEMENTS = new Set([ 'area', @@ -97,93 +57,6 @@ class ExpressionTagPrinter extends BaseHtmlNodePrinter { } class ElementPrinter extends BaseHtmlNodePrinter { - private printAttributes(attribute: AST.Attribute | AST.SpreadAttribute | Directive, context: PrinterContext) { - const { write } = context; - - if (attribute.type === 'Attribute') { - if (attribute.value === true) { - return; - } - - if (_.isArray(attribute.value)) { - const [value] = attribute.value; - - if (value.type === 'Text') { - write(` ${attribute.name}="${value.data}"`); - } - - if (value.type === 'ExpressionTag') { - write(` ${attribute.name}={${generate(value.expression, context.indent)}}`); - } - } else { - write(` ${attribute.name}={${generate(attribute.value.expression, context.indent)}}`); - } - } else if (attribute.type === 'SpreadAttribute') { - write(` {...${generate(attribute.expression, context.indent)}}`); - } else if (attribute.type === 'AnimateDirective') { - if (attribute.expression) { - write(` animate:${attribute.name}={${generate(attribute.expression, context.indent)}}`); - } else { - write(` animate:${attribute.name}`); - } - } else if (attribute.type === 'BindDirective') { - write(` bind:${attribute.name}={${generate(attribute.expression, context.indent)}}`); - } else if (attribute.type === 'ClassDirective') { - write(` class:${attribute.name}={${generate(attribute.expression, context.indent)}}`); - } else if (attribute.type === 'LetDirective') { - if (attribute.expression) { - write(` let:${attribute.name}={${generate(attribute.expression, context.indent)}}`); - } else { - write(` let:${attribute.name}`); - } - } else if (attribute.type === 'OnDirective') { - if (attribute.expression) { - write(` on:${attribute.name}={${generate(attribute.expression, context.indent)}}`); - } else { - write(` on:${attribute.name}`); - } - } else if (attribute.type === 'StyleDirective') { - if (attribute.value === true) { - write(` style:${attribute.name}`); - } else { - if (_.isArray(attribute.value)) { - const [value] = attribute.value; - if (value.type === 'Text') { - write(` style:${attribute.name}="${value.data}"`); - } else { - write(` style:${attribute.name}={${generate(value, context.indent)}}`); - } - } else { - write(` style:${attribute.name}={${generate(attribute.value.expression, context.indent)}}`); - } - } - } else if (attribute.type === 'TransitionDirective') { - const transition = () => { - if (attribute.intro && !attribute.outro) { - return 'in'; - } - - if (attribute.outro && !attribute.intro) { - return 'out'; - } - - return 'transition'; - }; - - if (attribute.expression) { - write(` ${transition()}:${attribute.name}={${generate(attribute.expression, context.indent)}}`); - } else { - write(` ${transition()}:${attribute.name}`); - } - } else if (attribute.type === 'UseDirective') { - if (attribute.expression) { - write(` use:${attribute.name}={${generate(attribute.expression, context.indent)}}`); - } else { - write(` use:${attribute.name}`); - } - } - } - enter(node: ElementLike, _: SvelteNode, context: PrinterContext) { const { write } = context; @@ -207,7 +80,7 @@ class ElementPrinter extends BaseHtmlNodePrinter { }); } - node.attributes.forEach(attribute => this.printAttributes(attribute, context)); + node.attributes.forEach(attribute => printAttributes(attribute, context)); if ( (node.type === 'Component' || diff --git a/src/print-script.ts b/src/print-script.ts index c3bbb28..aa68d3f 100644 --- a/src/print-script.ts +++ b/src/print-script.ts @@ -1,23 +1,26 @@ import { generate } from 'astring'; import type { AST } from 'svelte/compiler'; import { DefaultPrinterIdentOptions, PrinterIdentOptions } from './index.js'; +import generator from '@vardario/astring-ts-generator'; +import { attributeToString } from './utils.js'; export default function printScript(root: AST.Root, indent: PrinterIdentOptions = DefaultPrinterIdentOptions): string { let result = ''; if (root.instance) { result += '', () => { @@ -23,4 +23,8 @@ describe(''); }); + + test('typescript instance', () => { + testScriptPrinter(''); + }); }); diff --git a/src/utils.ts b/src/utils.ts index dd75fa8..5c45d5e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,155 @@ +import { AST } from 'svelte/compiler'; +import { PrinterContext } from './print-html'; +import { generate } from 'astring'; +import _ from 'lodash'; + +export type ElementLike = + | AST.Component + | AST.TitleElement + | AST.SlotElement + | AST.RegularElement + | AST.SvelteBody + | AST.SvelteComponent + | AST.SvelteDocument + | AST.SvelteElement + | AST.SvelteFragment + | AST.SvelteHead + | AST.SvelteOptionsRaw + | AST.SvelteSelf + | AST.SvelteWindow; + +export type Directive = + | AST.AnimateDirective + | AST.BindDirective + | AST.ClassDirective + | AST.LetDirective + | AST.OnDirective + | AST.StyleDirective + | AST.TransitionDirective + | AST.UseDirective; + +export type Tag = AST.ExpressionTag | AST.HtmlTag | AST.ConstTag | AST.DebugTag | AST.RenderTag; +export type Block = AST.EachBlock | AST.IfBlock | AST.AwaitBlock | AST.KeyBlock | AST.SnippetBlock; + +export type TemplateNode = + | AST.Root + | AST.Text + | Tag + | ElementLike + | AST.Attribute + | AST.SpreadAttribute + | Directive + | AST.Comment + | Block; + +export type SvelteNode = Node | TemplateNode | AST.Fragment | any; + export function identLiteral(level: number, ident: string) { return new Array(level).join(ident); } + +export function printAttributes(attribute: AST.Attribute | AST.SpreadAttribute | Directive, context: PrinterContext) { + const { write } = context; + + if (attribute.type === 'Attribute') { + if (attribute.value === true) { + return; + } + + if (_.isArray(attribute.value)) { + const [value] = attribute.value; + + if (value.type === 'Text') { + write(` ${attribute.name}="${value.data}"`); + } + + if (value.type === 'ExpressionTag') { + write(` ${attribute.name}={${generate(value.expression, context.indent)}}`); + } + } else { + write(` ${attribute.name}={${generate(attribute.value.expression, context.indent)}}`); + } + } else if (attribute.type === 'SpreadAttribute') { + write(` {...${generate(attribute.expression, context.indent)}}`); + } else if (attribute.type === 'AnimateDirective') { + if (attribute.expression) { + write(` animate:${attribute.name}={${generate(attribute.expression, context.indent)}}`); + } else { + write(` animate:${attribute.name}`); + } + } else if (attribute.type === 'BindDirective') { + write(` bind:${attribute.name}={${generate(attribute.expression, context.indent)}}`); + } else if (attribute.type === 'ClassDirective') { + write(` class:${attribute.name}={${generate(attribute.expression, context.indent)}}`); + } else if (attribute.type === 'LetDirective') { + if (attribute.expression) { + write(` let:${attribute.name}={${generate(attribute.expression, context.indent)}}`); + } else { + write(` let:${attribute.name}`); + } + } else if (attribute.type === 'OnDirective') { + if (attribute.expression) { + write(` on:${attribute.name}={${generate(attribute.expression, context.indent)}}`); + } else { + write(` on:${attribute.name}`); + } + } else if (attribute.type === 'StyleDirective') { + if (attribute.value === true) { + write(` style:${attribute.name}`); + } else { + if (_.isArray(attribute.value)) { + const [value] = attribute.value; + if (value.type === 'Text') { + write(` style:${attribute.name}="${value.data}"`); + } else { + write(` style:${attribute.name}={${generate(value, context.indent)}}`); + } + } else { + write(` style:${attribute.name}={${generate(attribute.value.expression, context.indent)}}`); + } + } + } else if (attribute.type === 'TransitionDirective') { + const transition = () => { + if (attribute.intro && !attribute.outro) { + return 'in'; + } + + if (attribute.outro && !attribute.intro) { + return 'out'; + } + + return 'transition'; + }; + + if (attribute.expression) { + write(` ${transition()}:${attribute.name}={${generate(attribute.expression, context.indent)}}`); + } else { + write(` ${transition()}:${attribute.name}`); + } + } else if (attribute.type === 'UseDirective') { + if (attribute.expression) { + write(` use:${attribute.name}={${generate(attribute.expression, context.indent)}}`); + } else { + write(` use:${attribute.name}`); + } + } +} + +export function attributeToString(attribute: AST.Attribute | AST.SpreadAttribute | Directive) { + let result = ''; + + const context: PrinterContext = { + _this: null, + write: (str: string) => { + return (result += str); + }, + indent: { + indent: '', + lineEnd: '' + } + }; + + printAttributes(attribute, context); + + return result; +}