diff --git a/package.json b/package.json index 1c18f2f..7b30508 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "private": true, "scripts": { - "build": "pnpm run build:parser && pnpm run build:language-service && pnpm run build:language-server && pnpm run build:typescript-plugin && pnpm run build:vscode", + "build": "pnpm run build:parser && pnpm run build:prettier-plugin && pnpm run build:language-service && pnpm run build:language-server && pnpm run build:typescript-plugin && pnpm run build:vscode", "build:parser": "cd ./packages/parser && pnpm build", + "build:prettier-plugin": "cd ./packages/prettier-plugin && pnpm build", "build:language-service": "cd ./packages/language-service && pnpm build", "build:language-server": "cd ./packages/language-server && pnpm build", "build:typescript-plugin": "cd ./packages/typescript-plugin && pnpm build", diff --git a/packages/prettier-plugin/.gitignore b/packages/prettier-plugin/.gitignore new file mode 100644 index 0000000..f1ff06d --- /dev/null +++ b/packages/prettier-plugin/.gitignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/prettier-plugin/package.json b/packages/prettier-plugin/package.json new file mode 100644 index 0000000..1cb81f7 --- /dev/null +++ b/packages/prettier-plugin/package.json @@ -0,0 +1,32 @@ +{ + "name": "prettier-plugin-tempblot", + "version": "0.0.1", + "type": "module", + "description": "Prettier plugin for Tempblot templates", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.js", + "default": "./lib/index.js" + } + }, + "files": [ + "lib" + ], + "scripts": { + "build": "tsc --build", + "prepack": "tsc --build --clean && tsc --build" + }, + "dependencies": { + "tempblot-parser": "workspace:*" + }, + "devDependencies": { + "@types/node": "^24.5.2", + "prettier": "^3.6.2", + "typescript": "^6.0.3" + }, + "peerDependencies": { + "prettier": ">=3.0.0" + }, + "sideEffects": false +} diff --git a/packages/prettier-plugin/src/index.ts b/packages/prettier-plugin/src/index.ts new file mode 100644 index 0000000..d8e29e0 --- /dev/null +++ b/packages/prettier-plugin/src/index.ts @@ -0,0 +1,96 @@ +import type { AstPath, Parser, Plugin, Printer } from 'prettier'; +import { parseTempblotRoot, type ParsedRoot, type RootBlock } from 'tempblot-parser'; + +interface TempblotAst { + type: 'TempblotDocument'; + source: string; + root: ParsedRoot; +} + +const parser: Parser = { + astFormat: 'tempblot-ast', + parse(source) { + return { + type: 'TempblotDocument', + source, + root: parseTempblotRoot(source), + }; + }, + locStart() { + return 0; + }, + locEnd(node) { + return node.source.length; + }, +}; + +const printer: Printer = { + print(path) { + return formatDocument(path); + }, +}; + +const plugin: Plugin = { + languages: [ + { + name: 'Tempblot', + parsers: ['tempblot'], + extensions: ['.blot'], + vscodeLanguageIds: ['tempblot'], + }, + ], + parsers: { + tempblot: parser, + }, + printers: { + 'tempblot-ast': printer, + }, +}; + +export default plugin; +export const languages = plugin.languages; +export const parsers = plugin.parsers; +export const printers = plugin.printers; + +function formatDocument(path: AstPath) { + const ast = path.node; + const blocks = ast.root.blocks; + + if (blocks.length === 0) { + return ensureTrailingNewline(ast.source.trim()); + } + + const leadingText = ast.source.slice(0, blocks[0].start).trim(); + const trailingText = ast.source.slice(blocks[blocks.length - 1].end).trim(); + const parts = [ + leadingText, + ...blocks.map((block) => formatBlock(ast.source, block)), + trailingText, + ].filter((part) => part.length > 0); + + return ensureTrailingNewline(parts.join('\n\n')); +} + +function formatBlock(source: string, block: RootBlock) { + const openTag = formatOpenTag(block); + const closeTag = ``; + const contents = source.slice(block.startTagEnd, block.endTagStart).trim(); + + if (!contents) { + return `${openTag}\n${closeTag}`; + } + + return `${openTag}\n${contents}\n${closeTag}`; +} + +function formatOpenTag(block: RootBlock) { + const attributes = Object.entries(block.attributes) + .map(([name, value]) => `${name}="${value}"`) + .join(' '); + + return attributes ? `<${block.tag} ${attributes}>` : `<${block.tag}>`; +} + +function ensureTrailingNewline(text: string) { + return `${text}\n`; +} diff --git a/packages/prettier-plugin/tsconfig.json b/packages/prettier-plugin/tsconfig.json new file mode 100644 index 0000000..d921b32 --- /dev/null +++ b/packages/prettier-plugin/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "declaration": true, + "outDir": "lib", + "rootDir": "src" + }, + "references": [{ "path": "../parser" }] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a4a3c2..366af16 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,6 +184,22 @@ importers: specifier: ^4.1.6 version: 4.1.6(@types/node@24.12.4)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(yaml@2.9.0)) + packages/prettier-plugin: + dependencies: + tempblot-parser: + specifier: workspace:* + version: link:../parser + devDependencies: + '@types/node': + specifier: ^24.5.2 + version: 24.12.4 + prettier: + specifier: ^3.6.2 + version: 3.8.3 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + packages/typescript-plugin: dependencies: '@volar/typescript':