diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5ca2d72..bd65b7fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,4 +32,4 @@ jobs: - run: pnpm build - - run: pnpm test:all + - run: pnpm test diff --git a/.gitignore b/.gitignore index 437f092a..cc33aa0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,12 @@ -.env +# compiled output +dist/ +dist-for-testing/ +tmp/ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* +# dependencies +node_modules/ -node_modules -dist -dist-ssr -*.local -.eslintcache -.npmrc - -# Editor directories and files -.idea +# misc .DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? +.env* +.eslintcache diff --git a/.npmignore b/.npmignore index 26a0d10c..4e342f02 100644 --- a/.npmignore +++ b/.npmignore @@ -1,12 +1,25 @@ +# compiled output +/dist-for-testing/ +/tmp/ + +# dependencies +/node_modules/ + +# misc +/.DS_Store +/.env* +/.eslintcache +/.git/ /.github/ -/.vscode/ -/examples/ -/src/ -/tests/ /.gitignore +/.pnpm-debug.log /.prettierignore /.release-it.json /.release-plan.json +/.vscode/ +/src/ +/tests/ +/tests-vitest/ /CODE_OF_CONDUCT.md /CONTRIBUTING.md /RELEASE.md @@ -17,6 +30,5 @@ /pnpm-lock.yaml /pnpm-workspace.yaml /prettier.config.mjs +/tsconfig.build.json /tsconfig.json -/tsconfig.lint.json -/vite.config.ts diff --git a/.prettierignore b/.prettierignore index 739b6778..9d7dcbe4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,9 +7,9 @@ pnpm-lock.yaml # specific to this package -/examples/ -/tests/__snapshots__/ -/tests/cases/ +/tests/fixtures/ +/tests-vitest/__snapshots__/ +/tests-vitest/cases/ CHANGELOG.md README.md RELEASE.md diff --git a/.release-it.json b/.release-it.json index 7e397a92..10a4ef12 100644 --- a/.release-it.json +++ b/.release-it.json @@ -14,7 +14,7 @@ "tokenRef": "GITHUB_AUTH" }, "hooks": { - "before:init": ["npm run lint", "npm run test:run"], + "before:init": ["npm run lint", "npm run test"], "after:bump": "npm run build", "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}." } diff --git a/@types/prettier/plugins/estree.d.ts b/@types/prettier/plugins/estree.d.ts deleted file mode 100644 index 508c98d5..00000000 --- a/@types/prettier/plugins/estree.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Printer } from 'prettier'; - -declare const printers: { - estree: Printer; -}; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a64a3f7..aa639bc1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,29 +22,15 @@ Find me on the [Ember Discord](https://discord.com/invite/emberjs) at `hm_krysta 1. Install dependencies: ```bash - pnpm install --recursive + pnpm install ``` 1. Run the tests to ensure your development environment is working properly: - Watch mode: - ```bash pnpm test ``` - Single run: - - ```bash - pnpm test:run - ``` - - Watch mode with a cute UI in the browser: - - ```bash - pnpm test:ui - ``` - ## Pull Requests We love pull requests. Here's a quick guide: @@ -79,25 +65,15 @@ We love pull requests. Here's a quick guide: NOTE: Partially copied from https://github.com/emberjs/ember.js/blob/master/CONTRIBUTING.md -## Examples - -1. Follow the [Development](#development) guide above to set up your environment. - -1. Edit `example.gjs` or `example.gts` to make the ugliest component file you can think of. - -1. Run `pnpm run example` or `pnpm run example-ts` to prettify your ugly file. 😍 (Note that `run example` will also build the project, so it may take a second.) - -1. Don't commit your changes to the example files. - ## Tests -Most of the tests are generated by formatting example files from the `cases` directory and comparing the output to [Vitest snapshots](https://vitest.dev/guide/snapshot.html). +Most of the tests are generated by formatting example files from the `tests-vitest/cases` directory and comparing the output to [Vitest snapshots](https://vitest.dev/guide/snapshot.html). To add a new case, add your case file(s) in the appropriate place(s) here: https://github.com/gitKrystan/prettier-plugin-ember-template-tag/tree/main/tests/cases New cases will be tested against a variety of configs. If you also want to test against for "ambiguous expressions" issues as described [here](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/issues/1), you can include the comment `/*AMBIGUOUS*/` anywhere in your test cases and it will be replaced by the ambiguous cases listed [here](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/tree/main/tests/helpers/ambiguous.ts) in a variety of generated tests. If you find a new ambiguous case, add it to that list. -Once you make your changes and/or add new cases `pnpm test:run -u` to update the snapshots then carefully inspect the results to ensure the output matches your expectations. +Once you make your changes and/or add new cases `pnpm test:vitest -u` to update the snapshots then carefully inspect the results to ensure the output matches your expectations. ## Prior Art and Useful Resources diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..5ba75bb2 --- /dev/null +++ b/build.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +ENVIRONMENT=$1 + +if [ $ENVIRONMENT = "--production" ] +then + # Clean slate + rm -rf "dist" + + # Compile TypeScript + tsc --project "tsconfig.build.json" + + echo "SUCCESS: Built dist.\n" + +elif [ $ENVIRONMENT = "--test" ] +then + # Clean slate + rm -rf "dist-for-testing" + + # Compile TypeScript + tsc --project "tsconfig.json" + + echo "SUCCESS: Built dist-for-testing.\n" + +fi diff --git a/eslint.config.mjs b/eslint.config.mjs index 74875a5c..f613a242 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -27,7 +27,6 @@ const customRules = { rules: { 'jsdoc/check-param-names': 'off', 'jsdoc/newline-after-description': 'off', - 'jsdoc/require-jsdoc': ['error', { publicOnly: true }], 'jsdoc/require-param': 'off', 'jsdoc/require-param-type': 'off', 'jsdoc/require-returns': 'off', @@ -55,11 +54,12 @@ export default tseslint.config( { ignores: [ 'dist/', + 'dist-for-testing/', 'node_modules/', + 'tests-vitest/__snapshots__/', + 'tests-vitest/cases/', + 'tmp/', '.*/', - 'tests/__snapshots__/', - 'tests/cases/', - 'vite.config.ts', ], }, { diff --git a/examples/bin/test.mjs b/examples/bin/test.mjs deleted file mode 100755 index f7f4006e..00000000 --- a/examples/bin/test.mjs +++ /dev/null @@ -1,117 +0,0 @@ -/* eslint-disable n/no-process-exit, unicorn/no-process-exit */ -import { spawn } from 'node:child_process'; -import fs from 'node:fs/promises'; -import path from 'node:path'; - -import chalk from 'chalk'; - -const debug = process.argv.includes('--debug'); - -const logDebug = (message) => { - if (debug) { - console.log(`[${chalk.blue('debug')}]`, message); - } -}; - -const newLine = () => console.log(); - -const inputDirectory = './input'; -const expectedOutputDirectory = './expected-output'; - -// NOTE: Run with `--debug` to get debug output (from both this script and prettier) -const run = async () => { - logDebug('Reading input directory...'); - - try { - const inputFiles = await fs.readdir(inputDirectory); - const originalFiles = new Map(); - - logDebug('Backing up input files...'); - for (const file of inputFiles) { - const filePath = path.join(inputDirectory, file); - const fileContent = await fs.readFile(filePath, 'utf8'); - originalFiles.set(file, fileContent); - } - - if (debug) { - newLine(); - } - console.log('💅 Running Prettier...'); - const prettier = spawn( - 'node', - [ - './node_modules/prettier/bin/prettier.cjs', - './input', - '--write', - debug ? '--log-level' : null, - debug ? 'debug' : null, - ].filter(Boolean), - { - env: { - ...process.env, - FORCE_COLOR: true, - }, - }, - ); - - prettier.stdout.pipe(process.stdout); - prettier.stderr.pipe(process.stderr); - - prettier.on('close', async (code) => { - if (code !== 0) { - console.error(chalk.red(`💩 Prettier exited with code ${code}`)); - process.exit(code); - } - - newLine(); - console.log('👯 Comparing files...'); - - let allFilesMatch = true; - - for (const file of inputFiles) { - const inputFilePath = path.join(inputDirectory, file); - const expectedOutputFilePath = path.join(expectedOutputDirectory, file); - - try { - const [inputContent, expectedOutputContent] = await Promise.all([ - fs.readFile(inputFilePath, 'utf8'), - fs.readFile(expectedOutputFilePath, 'utf8'), - ]); - - if (inputContent === expectedOutputContent) { - console.log( - chalk.green('Pass'), - `${file}: Formatted content matches expected output.`, - ); - logDebug(`Resetting ${file}...`); - await fs.writeFile(inputFilePath, originalFiles.get(file)); - } else { - console.log( - chalk.red('Fail'), - `${file}: Formatted content does not match expected output.`, - ); - allFilesMatch = false; - } - } catch (error) { - console.error(chalk.red(`Error processing ${file}:`), error); - allFilesMatch = false; - } - } - - newLine(); - - if (allFilesMatch) { - console.log('👋 Exiting...', chalk.green('SUCCESS')); - process.exit(0); - } else { - console.log('👋 Exiting...', chalk.red('FAIL')); - process.exit(1); - } - }); - } catch (error) { - console.error(chalk.red('💩 Error:'), error); - process.exit(2); - } -}; - -run(); diff --git a/examples/package.json b/examples/package.json deleted file mode 100644 index 0a0f35b4..00000000 --- a/examples/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "-examples", - "private": true, - "version": "0.0.0", - "scripts": { - "example": "pnpm prettier ./input --write", - "example-debug": "pnpm prettier ./input --write --log-level debug", - "test": "node ./bin/test.mjs" - }, - "dependencies": { - "prettier": "^3.5.3", - "prettier-plugin-ember-template-tag": "workspace:^" - }, - "devDependencies": { - "chalk": "^5.3.0" - } -} diff --git a/examples/pnpm-lock.yaml b/examples/pnpm-lock.yaml deleted file mode 100644 index 3f95ccbb..00000000 --- a/examples/pnpm-lock.yaml +++ /dev/null @@ -1,10 +0,0 @@ -lockfileVersion: '6.1' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - prettier-plugin-ember-template-tag: - specifier: workspace:^ - version: link:.. diff --git a/examples/prettier.config.mjs b/examples/prettier.config.mjs deleted file mode 100644 index 57057a05..00000000 --- a/examples/prettier.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -export default { - plugins: ['prettier-plugin-ember-template-tag'], - singleQuote: true, -}; diff --git a/package.json b/package.json index 44c84f92..dc6ca8ef 100644 --- a/package.json +++ b/package.json @@ -27,24 +27,20 @@ "email": "kmenne@gmail.com" }, "type": "module", - "main": "dist/prettier-plugin-ember-template-tag.js", + "main": "dist/src/main.js", "scripts": { - "build": "tsc && vite build", - "example": "pnpm preexample && cd examples && pnpm example", + "build": "./build.sh --production", "format": "prettier --cache --write .", "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefix-colors auto && pnpm lint:format", "lint:js": "eslint . --cache", "lint:js:fix": "eslint . --fix", "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefix-colors auto && pnpm format", "lint:format": "prettier --cache --check .", - "lint:types": "tsc --project tsconfig.lint.json", + "lint:types": "tsc --noEmit", "prepack": "pnpm build", - "preexample": "vite build", - "test": "vitest", - "test:all": "concurrently \"pnpm:test:run\" \"pnpm:test:example\" --prefix-colors auto", - "test:example": "pnpm preexample && cd examples && pnpm test", - "test:run": "vitest run", - "test:ui": "vitest --ui" + "test": "concurrently \"pnpm:test:*\" --names \"test:\" --prefix-colors auto", + "test:fixtures": "./build.sh --test && mt dist-for-testing --quiet", + "test:vitest": "vitest run --exclude dist-for-testing --exclude tests" }, "dependencies": { "@babel/core": "^7.27.4", @@ -53,7 +49,10 @@ "devDependencies": { "@babel/eslint-parser": "^7.27.5", "@babel/types": "^7.27.6", + "@codemod-utils/files": "^3.0.2", + "@codemod-utils/tests": "^2.0.1", "@eslint/js": "^9.28.0", + "@sondr3/minitest": "^0.1.2", "@tsconfig/node18": "^18.2.4", "@tsconfig/strictest": "^2.0.5", "@types/babel__core": "^7.20.5", @@ -61,7 +60,6 @@ "@typescript-eslint/eslint-plugin": "^8.34.0", "@typescript-eslint/parser": "^8.34.0", "@vitest/eslint-plugin": "^1.2.4", - "@vitest/ui": "^3.2.3", "concurrently": "^9.1.2", "eslint": "^9.28.0", "eslint-config-prettier": "^10.1.5", @@ -75,7 +73,6 @@ "release-plan": "^0.11.0", "typescript": "^5.8.3", "typescript-eslint": "^8.34.0", - "vite": "^6.3.5", "vitest": "^3.2.3" }, "peerDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 201bc243..5e6385c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,9 +21,18 @@ importers: '@babel/types': specifier: ^7.27.6 version: 7.27.6 + '@codemod-utils/files': + specifier: ^3.0.2 + version: 3.0.2 + '@codemod-utils/tests': + specifier: ^2.0.1 + version: 2.0.1(@sondr3/minitest@0.1.2) '@eslint/js': specifier: ^9.28.0 version: 9.28.0 + '@sondr3/minitest': + specifier: ^0.1.2 + version: 0.1.2 '@tsconfig/node18': specifier: ^18.2.4 version: 18.2.4 @@ -45,9 +54,6 @@ importers: '@vitest/eslint-plugin': specifier: ^1.2.4 version: 1.2.4(eslint@9.28.0)(typescript@5.8.3)(vitest@3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.3)) - '@vitest/ui': - specifier: ^3.2.3 - version: 3.2.3(vitest@3.2.3) concurrently: specifier: ^9.1.2 version: 9.1.2 @@ -87,27 +93,11 @@ importers: typescript-eslint: specifier: ^8.34.0 version: 8.34.0(eslint@9.28.0)(typescript@5.8.3) - vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.31) vitest: specifier: ^3.2.3 version: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.3) - examples: - dependencies: - prettier: - specifier: ^3.5.3 - version: 3.5.3 - prettier-plugin-ember-template-tag: - specifier: workspace:^ - version: link:.. - devDependencies: - chalk: - specifier: ^5.3.0 - version: 5.4.1 - - tests: + tests-vitest: dependencies: prettier-plugin-ember-template-tag: specifier: workspace:^ @@ -200,6 +190,16 @@ packages: resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} engines: {node: '>=6.9.0'} + '@codemod-utils/files@3.0.2': + resolution: {integrity: sha512-edJ+afzVyEUDVu2uqQwZF9VXHT4ErYMn7nLhfBqjp79M5dcsBRRWBms5ifQDDiXbgxhAptXEmOsfEwVJXA5z+Q==} + engines: {node: 20.* || >= 22} + + '@codemod-utils/tests@2.0.1': + resolution: {integrity: sha512-i/3JI411xQBIRgWxhO0ei8MVY/+C7hMPfT6zTUAMU1KfxxR4oCw8eVccmtr+XA76Sf2td5oTITvRo0IP2c84eg==} + engines: {node: 20.* || >= 22} + peerDependencies: + '@sondr3/minitest': ^0.1.2 + '@es-joy/jsdoccomment@0.50.2': resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==} engines: {node: '>=18'} @@ -423,6 +423,14 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -668,6 +676,11 @@ packages: cpu: [x64] os: [win32] + '@sondr3/minitest@0.1.2': + resolution: {integrity: sha512-Ru43wBFch0GWPCtGTUEh8oGvC1fdAKyOQFmgwOZEFhoyR/+mk3vpRzh5ldEYdfTW4mlqdDNOf6TZWnIb17QOzw==} + engines: {node: '>=18'} + hasBin: true + '@tootallnate/once@1.1.2': resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} @@ -705,18 +718,33 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/fs-extra@9.0.13': + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + + '@types/glob@8.1.0': + resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/minimatch@3.0.5': + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + + '@types/minimatch@5.1.2': + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} '@types/node@22.15.31': resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==} + '@types/rimraf@3.0.2': + resolution: {integrity: sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -934,10 +962,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} @@ -1063,6 +1087,9 @@ packages: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} + ensure-posix-path@1.1.1: + resolution: {integrity: sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==} + err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -1239,6 +1266,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + fixturify@3.0.0: + resolution: {integrity: sha512-PFOf/DT9/t2NCiVyiQ5cBMJtGZfWh3aeOV8XVqQQOPBlTv8r6l0k75/hm36JOaiJlrWFk/8aYFyOKAvOkrkjrw==} + engines: {node: 14.* || >= 16.*} + flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -1246,8 +1277,8 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} fs-extra@10.1.0: @@ -1302,6 +1333,11 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -1458,6 +1494,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} @@ -1540,6 +1580,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1554,6 +1598,10 @@ packages: resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} engines: {node: '>= 10'} + matcher-collection@2.0.1: + resolution: {integrity: sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==} + engines: {node: 6.* || 8.* || >= 10.*} + mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} @@ -1642,6 +1690,10 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1827,6 +1879,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2274,6 +2330,10 @@ packages: jsdom: optional: true + walk-sync@3.0.0: + resolution: {integrity: sha512-41TvKmDGVpm2iuH7o+DAOt06yyu/cSHpX3uzAwetzASvlNtVddgIjXIb2DfB/Wa20B1Jo86+1Dv1CraSU7hWdw==} + engines: {node: 10.* || >= 12.*} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -2451,6 +2511,16 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@codemod-utils/files@3.0.2': + dependencies: + glob: 11.0.3 + + '@codemod-utils/tests@2.0.1(@sondr3/minitest@0.1.2)': + dependencies: + '@sondr3/minitest': 0.1.2 + fixturify: 3.0.0 + glob: 11.0.3 + '@es-joy/jsdoccomment@0.50.2': dependencies: '@types/estree': 1.0.8 @@ -2602,6 +2672,12 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2795,7 +2871,8 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@polka/url@1.0.0-next.29': {} + '@polka/url@1.0.0-next.29': + optional: true '@rollup/rollup-android-arm-eabi@4.40.1': optional: true @@ -2857,6 +2934,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.1': optional: true + '@sondr3/minitest@0.1.2': {} + '@tootallnate/once@1.1.2': {} '@tsconfig/node18@18.2.4': {} @@ -2898,18 +2977,36 @@ snapshots: '@types/estree@1.0.8': {} + '@types/fs-extra@9.0.13': + dependencies: + '@types/node': 22.15.31 + + '@types/glob@8.1.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 22.15.31 + '@types/json-schema@7.0.15': {} '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/minimatch@3.0.5': {} + + '@types/minimatch@5.1.2': {} + '@types/ms@2.1.0': {} '@types/node@22.15.31': dependencies: undici-types: 6.21.0 + '@types/rimraf@3.0.2': + dependencies: + '@types/glob': 8.1.0 + '@types/node': 22.15.31 + '@types/unist@3.0.3': {} '@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.28.0)(typescript@5.8.3))(eslint@9.28.0)(typescript@5.8.3)': @@ -3060,6 +3157,7 @@ snapshots: tinyglobby: 0.2.14 tinyrainbow: 2.0.0 vitest: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.3) + optional: true '@vitest/utils@3.2.3': dependencies: @@ -3185,8 +3283,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.4.1: {} - character-entities@2.0.2: {} check-error@2.1.1: {} @@ -3305,6 +3401,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 + ensure-posix-path@1.1.1: {} + err-code@2.0.3: {} es-module-lexer@1.7.0: {} @@ -3545,7 +3643,8 @@ snapshots: optionalDependencies: picomatch: 4.0.2 - fflate@0.8.2: {} + fflate@0.8.2: + optional: true file-entry-cache@8.0.0: dependencies: @@ -3562,6 +3661,15 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + fixturify@3.0.0: + dependencies: + '@types/fs-extra': 9.0.13 + '@types/minimatch': 3.0.5 + '@types/rimraf': 3.0.2 + fs-extra: 10.1.0 + matcher-collection: 2.0.1 + walk-sync: 3.0.0 + flat-cache@4.0.1: dependencies: flatted: 3.3.3 @@ -3569,7 +3677,7 @@ snapshots: flatted@3.3.3: {} - foreground-child@3.3.0: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 @@ -3628,13 +3736,22 @@ snapshots: glob@10.4.5: dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3763,6 +3880,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + jju@1.4.0: {} js-tokens@4.0.0: {} @@ -3824,6 +3945,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.1.0: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -3858,6 +3981,11 @@ snapshots: - bluebird - supports-color + matcher-collection@2.0.1: + dependencies: + '@types/minimatch': 3.0.5 + minimatch: 3.1.2 + mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 @@ -4025,6 +4153,10 @@ snapshots: min-indent@1.0.1: {} + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -4074,7 +4206,8 @@ snapshots: mkdirp@1.0.4: {} - mrmime@2.0.1: {} + mrmime@2.0.1: + optional: true ms@2.1.3: {} @@ -4203,6 +4336,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-scurry@2.0.0: + dependencies: + lru-cache: 11.1.0 + minipass: 7.1.2 + pathe@2.0.3: {} pathval@2.0.0: {} @@ -4372,6 +4510,7 @@ snapshots: '@polka/url': 1.0.0-next.29 mrmime: 2.0.1 totalist: 3.0.1 + optional: true smart-buffer@4.2.0: {} @@ -4499,7 +4638,8 @@ snapshots: dependencies: is-number: 7.0.0 - totalist@3.0.1: {} + totalist@3.0.1: + optional: true tr46@0.0.3: {} @@ -4643,6 +4783,13 @@ snapshots: - tsx - yaml + walk-sync@3.0.0: + dependencies: + '@types/minimatch': 3.0.5 + ensure-posix-path: 1.1.1 + matcher-collection: 2.0.1 + minimatch: 3.1.2 + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4165e551..983fae0a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,3 @@ packages: - . - - tests - - examples + - tests-vitest diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index 5c90b666..00000000 --- a/src/config.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const PARSER_NAME = 'ember-template-tag'; -export const PRINTER_NAME = 'ember-template-tag-estree'; -export const TEMPLATE_TAG_NAME = 'template'; - -export const TEMPLATE_TAG_OPEN = `<${TEMPLATE_TAG_NAME}>`; -export const TEMPLATE_TAG_CLOSE = ``; diff --git a/src/languages.ts b/src/languages.ts new file mode 100644 index 00000000..536de8ee --- /dev/null +++ b/src/languages.ts @@ -0,0 +1,22 @@ +import type { SupportLanguage } from 'prettier'; + +import { PARSER_NAME } from './utils/index.js'; + +export const languages: SupportLanguage[] = [ + { + aliases: ['gjs', 'glimmer-js'], + extensions: ['.gjs'], + group: 'JavaScript', + name: 'Ember Template Tag (gjs)', + parsers: [PARSER_NAME], + vscodeLanguageIds: ['glimmer-js'], + }, + { + aliases: ['gts', 'glimmer-ts'], + extensions: ['.gts'], + group: 'TypeScript', + name: 'Ember Template Tag (gts)', + parsers: [PARSER_NAME], + vscodeLanguageIds: ['glimmer-ts'], + }, +]; diff --git a/src/main.ts b/src/main.ts index 0af8e15d..b6599ebc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,43 +1,4 @@ -import type { Node } from '@babel/types'; -import type { Parser, Plugin, Printer, SupportLanguage } from 'prettier'; - -import { PARSER_NAME, PRINTER_NAME } from './config.js'; -import { options } from './options.js'; -import { parser } from './parse/index.js'; -import { printer } from './print/index.js'; - -const languages: SupportLanguage[] = [ - { - name: 'Ember Template Tag (gjs)', - aliases: ['gjs', 'glimmer-js'], - extensions: ['.gjs'], - vscodeLanguageIds: ['glimmer-js'], - parsers: [PARSER_NAME], - group: 'JavaScript', - }, - { - name: 'Ember Template Tag (gts)', - aliases: ['gts', 'glimmer-ts'], - extensions: ['.gts'], - vscodeLanguageIds: ['glimmer-ts'], - parsers: [PARSER_NAME], - group: 'TypeScript', - }, -]; - -const parsers: Record> = { - [PARSER_NAME]: parser, -}; - -const printers: Record> = { - [PRINTER_NAME]: printer, -}; - -const plugin: Plugin = { - languages, - parsers, - printers, - options, -}; - -export default plugin; +export { languages } from './languages.js'; +export { options } from './options.js'; +export { parsers } from './parsers.js'; +export { printers } from './printers.js'; diff --git a/src/options.ts b/src/options.ts index 9a5b70da..db687a33 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,40 +1,28 @@ -import type { Node } from '@babel/types'; import type { BooleanSupportOption, ParserOptions, SupportOptions, } from 'prettier'; -export interface Options extends ParserOptions { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface PluginOptions extends ParserOptions { templateExportDefault?: boolean; templateSingleQuote?: boolean; } const templateExportDefault: BooleanSupportOption = { category: 'Format', - type: 'boolean', default: false, description: 'Prepend default export template tags with "export default". Since 0.1.0.', + type: 'boolean', }; -/** - * Extracts a valid `templateSingleQuote` option out of the provided options. If - * `templateSingleQuote` is defined, it will be used, otherwise the value for - * `singleQuote` will be inherited. - */ -export function getTemplateSingleQuote(options: Options): boolean { - const { singleQuote, templateSingleQuote } = options; - return typeof templateSingleQuote === 'boolean' - ? templateSingleQuote - : singleQuote; -} - const templateSingleQuote: BooleanSupportOption = { category: 'Format', - type: 'boolean', description: 'Use single quotes instead of double quotes within template tags. Since 0.0.3.', + type: 'boolean', }; export const options: SupportOptions = { diff --git a/src/parsers.ts b/src/parsers.ts new file mode 100644 index 00000000..b2594f75 --- /dev/null +++ b/src/parsers.ts @@ -0,0 +1,27 @@ +import type { File } from '@babel/types'; +import type { Parser, ParserOptions } from 'prettier'; +import { parsers as prettierParsers } from 'prettier/plugins/babel'; + +import { convertAst, preprocess } from './parsers/index.js'; +import { assert } from './utils/assert.js'; +import { type NodeType, PARSER_NAME, PRINTER_NAME } from './utils/index.js'; + +const parser = prettierParsers['babel-ts'] as Parser; + +async function parse(text: string, options: ParserOptions) { + const preprocessed = preprocess(text, options.filepath); + + const ast = await parser.parse(preprocessed.code, options); + assert('expected ast', ast); + convertAst(ast as File, preprocessed.templates); + + return ast; +} + +export const parsers: Record> = { + [PARSER_NAME]: { + ...parser, + astFormat: PRINTER_NAME, + parse, + }, +}; diff --git a/src/parse/index.ts b/src/parsers/convert-ast.ts similarity index 71% rename from src/parse/index.ts rename to src/parsers/convert-ast.ts index 833aa683..56e25a39 100644 --- a/src/parse/index.ts +++ b/src/parsers/convert-ast.ts @@ -2,19 +2,12 @@ import { traverse } from '@babel/core'; import type { BlockStatement, File, - Node, ObjectExpression, StaticBlock, } from '@babel/types'; -import type { Parser } from 'prettier'; -import { parsers as babelParsers } from 'prettier/plugins/babel.js'; -import { PRINTER_NAME } from '../config.js'; -import type { Options } from '../options.js'; import { assert } from '../utils/assert.js'; -import { preprocess, type Template } from './preprocess.js'; - -const typescript = babelParsers['babel-ts'] as Parser; +import type { Template } from './preprocess.js'; /** Converts a node into a GlimmerTemplate node */ function convertNode( @@ -29,7 +22,7 @@ function convertNode( } /** Traverses the AST and replaces the transformed template parts with other AST */ -function convertAst(ast: File, templates: Template[]): void { +export function convertAst(ast: File, templates: Template[]): void { traverse(ast, { enter(path) { const { node } = path; @@ -89,18 +82,3 @@ function convertAst(ast: File, templates: Template[]): void { ); } } - -export const parser: Parser = { - ...typescript, - astFormat: PRINTER_NAME, - - async parse(code: string, options: Options): Promise { - const preprocessed = preprocess(code, options.filepath); - - const ast = await typescript.parse(preprocessed.code, options); - assert('expected ast', ast); - convertAst(ast as File, preprocessed.templates); - - return ast; - }, -}; diff --git a/src/parsers/index.ts b/src/parsers/index.ts new file mode 100644 index 00000000..0ffcf919 --- /dev/null +++ b/src/parsers/index.ts @@ -0,0 +1,2 @@ +export * from './convert-ast.js'; +export * from './preprocess.js'; diff --git a/src/parse/preprocess.ts b/src/parsers/preprocess.ts similarity index 100% rename from src/parse/preprocess.ts rename to src/parsers/preprocess.ts diff --git a/src/print/ambiguity.ts b/src/print/ambiguity.ts deleted file mode 100644 index 7ff879f2..00000000 --- a/src/print/ambiguity.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { Node } from '@babel/types'; -import type { AstPath, doc, Printer } from 'prettier'; -import { printers as estreePrinters } from 'prettier/plugins/estree.js'; - -import type { Options } from '../options.js'; -import { flattenDoc } from '../utils/doc.js'; - -const estreePrinter = estreePrinters['estree'] as Printer; - -/** - * Search next non EmptyStatement node and set current print, so we can fix it - * later if its ambiguous - */ -export function saveCurrentPrintOnSiblingNode( - path: AstPath, - printed: doc.builders.Doc[], -): void { - const { index, siblings } = path; - if (index !== null) { - const nextNode = siblings - ?.slice(index + 1) - .find((n) => n?.type !== 'EmptyStatement'); - if (nextNode) { - nextNode.extra = nextNode.extra || {}; - nextNode.extra['prevTemplatePrinted'] = printed; - } - } -} - -/** HACK to fix ASI semi-colons. */ -export function fixPreviousPrint( - previousTemplatePrinted: doc.builders.Doc[], - path: AstPath, - options: Options, - print: (path: AstPath) => doc.builders.Doc, - args: unknown, -): void { - const printedSemiFalse = estreePrinter.print( - path, - { ...options, semi: false }, - print, - args, - ); - const flat = flattenDoc(printedSemiFalse); - const previousFlat = flattenDoc(previousTemplatePrinted); - if (flat[0]?.startsWith(';') && previousFlat.at(-1) !== ';') { - previousTemplatePrinted.push(';'); - } -} diff --git a/src/print/index.ts b/src/print/index.ts deleted file mode 100644 index 30b39901..00000000 --- a/src/print/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import type { Node } from '@babel/types'; -import type { - AstPath, - doc, - Options as PrettierOptions, - Printer, -} from 'prettier'; -import { printers as estreePrinters } from 'prettier/plugins/estree.js'; - -import { TEMPLATE_TAG_CLOSE, TEMPLATE_TAG_OPEN } from '../config.js'; -import type { Options } from '../options.js'; -import { - isGlimmerTemplate, - isGlimmerTemplateParent, -} from '../types/glimmer.js'; -import { assert } from '../utils/assert.js'; -import { - fixPreviousPrint, - saveCurrentPrintOnSiblingNode, -} from './ambiguity.js'; -import { printTemplateContent, printTemplateTag } from './template.js'; - -const estreePrinter = estreePrinters['estree'] as Printer; - -export const printer: Printer = { - ...estreePrinter, - - getVisitorKeys(node, nonTraversableKeys) { - if (node && isGlimmerTemplate(node)) { - return []; - } - return estreePrinter.getVisitorKeys?.(node, nonTraversableKeys) || []; - }, - - print( - path: AstPath, - options: Options, - print: (path: AstPath) => doc.builders.Doc, - args: unknown, - ) { - const { node } = path; - - if (isGlimmerTemplateParent(node)) { - if (checkPrettierIgnore(path)) { - return printRawText(path, options); - } else { - let printed = estreePrinter.print(path, options, print, args); - - assert('Expected Glimmer doc to be an array', Array.isArray(printed)); - trimPrinted(printed); - - // Remove semicolons so we can manage them ourselves - if (docMatchesString(printed[0], ';')) { - printed.shift(); - } - if (docMatchesString(printed.at(-1), ';')) { - printed.pop(); - } - - trimPrinted(printed); - - // Always remove export default so we start with a blank slate - if ( - docMatchesString(printed[0], 'export') && - docMatchesString(printed[1], 'default') - ) { - printed = printed.slice(2); - trimPrinted(printed); - } - - if (options.templateExportDefault) { - printed.unshift('export ', 'default '); - } - - saveCurrentPrintOnSiblingNode(path, printed); - - return printed; - } - } - - if (options.semi && node?.extra?.['prevTemplatePrinted']) { - fixPreviousPrint( - node.extra['prevTemplatePrinted'] as doc.builders.Doc[], - path, - options, - print, - args, - ); - } - - return estreePrinter.print(path, options, print, args); - }, - - /** Prints embedded GlimmerExpressions/GlimmerTemplates. */ - embed(path: AstPath, embedOptions: PrettierOptions) { - const { node } = path; - - return async (textToDoc) => { - if (node && isGlimmerTemplate(node)) { - if (checkPrettierIgnore(path)) { - return printRawText(path, embedOptions as Options); - } - - try { - const content = await printTemplateContent( - node.extra.template.contents, - textToDoc, - embedOptions as Options, - ); - - const printed = printTemplateTag(content); - saveCurrentPrintOnSiblingNode(path, printed); - return printed; - } catch (error) { - console.error(error); - const printed = [printRawText(path, embedOptions as Options)]; - saveCurrentPrintOnSiblingNode(path, printed); - return printed; - } - } - - // Nothing to embed, so move on to the regular printer. - return; - }; - }, -}; - -/** Remove the empty strings that Prettier added so we can manage them. */ -function trimPrinted(printed: doc.builders.Doc[]): void { - while (docMatchesString(printed[0], '')) { - printed.shift(); - } - - while (docMatchesString(printed.at(-1), '')) { - printed.pop(); - } -} - -function printRawText( - { node }: AstPath, - options: Options, -): string { - if (!node) { - return ''; - } - if (isGlimmerTemplate(node)) { - return ( - TEMPLATE_TAG_OPEN + node.extra.template.contents + TEMPLATE_TAG_CLOSE - ); - } - assert('expected start', typeof node.start == 'number'); - assert('expected end', typeof node.end == 'number'); - return options.originalText.slice(node.start, node.end); -} - -function hasPrettierIgnore(path: AstPath): boolean { - return path.node?.leadingComments?.at(-1)?.value.trim() === 'prettier-ignore'; -} - -function checkPrettierIgnore(path: AstPath): boolean { - return ( - hasPrettierIgnore(path) || - (!!path.getParentNode() && - path.callParent((parent) => checkPrettierIgnore(parent))) - ); -} - -function docMatchesString( - doc: doc.builders.Doc | undefined, - string: string, -): doc is string { - return typeof doc === 'string' && doc.trim() === string; -} diff --git a/src/print/template.ts b/src/print/template.ts deleted file mode 100644 index 39499d55..00000000 --- a/src/print/template.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { Options as PrettierOptions } from 'prettier'; -import { doc } from 'prettier'; - -import { TEMPLATE_TAG_CLOSE, TEMPLATE_TAG_OPEN } from '../config.js'; -import type { Options } from '../options.js'; -import { getTemplateSingleQuote } from '../options.js'; -import { flattenDoc } from '../utils/doc.js'; - -const { - builders: { group, hardline, indent, softline }, -} = doc; - -/** - * Returns a Prettier `Doc` for the given `TemplateLiteral` contents formatted - * using Prettier's built-in glimmer parser. - * - * NOTE: The contents are not surrounded with "`" - */ -export async function printTemplateContent( - text: string, - textToDoc: ( - text: string, - // Don't use our `Options` here even though technically they are available - // because we don't want to accidentally pass them into `textToDoc`. We - // should normalize them into standard Prettier options at this point. - options: PrettierOptions, - ) => Promise, - options: Options, -): Promise { - return await textToDoc(text.trim(), { - ...options, - parser: 'glimmer', - singleQuote: getTemplateSingleQuote(options), - }); -} - -/** - * Prints the given template content as a template tag. - * - * If `useHardline` is `true`, will use Prettier's hardline builder to force - * each tag to print on a new line. - * - * If `useHardline` is `false`, will use Prettier's softline builder to allow - * the tags to print on the same line if they fit. - */ -export function printTemplateTag( - content: doc.builders.Doc, -): doc.builders.Doc[] { - const contents = flattenDoc(content); - const useHardline = contents.some( - (c) => - // contains angle bracket tag - /<.+>/.test(c) || - // contains hbs block - /{{~?#.+}}/.test(c), - ); - const line = useHardline ? hardline : softline; - const doc = [ - TEMPLATE_TAG_OPEN, - indent([line, group(content)]), - line, - TEMPLATE_TAG_CLOSE, - ]; - return [group(doc)]; -} diff --git a/src/printers.ts b/src/printers.ts new file mode 100644 index 00000000..5b6c69ec --- /dev/null +++ b/src/printers.ts @@ -0,0 +1,136 @@ +import { type AstPath, doc as AST, type Printer } from 'prettier'; +import { printers as prettierPrinters } from 'prettier/plugins/estree'; + +import type { PluginOptions } from './options.js'; +import { + checkPrettierIgnore, + docMatchesString, + fixPreviousPrint, + printRawText, + printTemplateContent, + printTemplateTag, + saveCurrentPrintOnSiblingNode, + trimPrinted, +} from './printers/index.js'; +import { isGlimmerTemplate, isGlimmerTemplateParent } from './types/glimmer.js'; +import { assert } from './utils/assert.js'; +import { type NodeType, PRINTER_NAME } from './utils/index.js'; + +const printer = prettierPrinters['estree'] as Printer; + +function embed(path: AstPath, options: PluginOptions) { + const { node } = path; + + return async ( + textToDoc: ( + text: string, + options: PluginOptions, + ) => Promise, + ) => { + if (node && isGlimmerTemplate(node)) { + if (checkPrettierIgnore(path)) { + return printRawText(path, options); + } + + try { + const content = await printTemplateContent( + node.extra.template.contents, + textToDoc, + options, + ); + + const printed = printTemplateTag(content); + saveCurrentPrintOnSiblingNode(path, printed); + return printed; + } catch (error) { + console.error(error); + const printed = [printRawText(path, options)]; + saveCurrentPrintOnSiblingNode(path, printed); + return printed; + } + } + + // Nothing to embed, so move on to the regular printer. + return; + }; +} + +function getVisitorKeys( + node: NodeType, + nonTraversableKeys: Set, +): string[] { + if (node && isGlimmerTemplate(node)) { + return []; + } + + return printer.getVisitorKeys?.(node, nonTraversableKeys) || []; +} + +function print( + path: AstPath, + options: PluginOptions, + print: (path: AstPath) => AST.builders.Doc, + args?: unknown, +): AST.builders.Doc { + const { node } = path; + + if (isGlimmerTemplateParent(node)) { + if (checkPrettierIgnore(path)) { + return printRawText(path, options); + } else { + let printed = printer.print(path, options, print, args); + + assert('Expected Glimmer doc to be an array', Array.isArray(printed)); + trimPrinted(printed); + + // Remove semicolons so we can manage them ourselves + if (docMatchesString(printed[0], ';')) { + printed.shift(); + } + if (docMatchesString(printed.at(-1), ';')) { + printed.pop(); + } + + trimPrinted(printed); + + // Always remove export default so we start with a blank slate + if ( + docMatchesString(printed[0], 'export') && + docMatchesString(printed[1], 'default') + ) { + printed = printed.slice(2); + trimPrinted(printed); + } + + if (options.templateExportDefault) { + printed.unshift('export ', 'default '); + } + + saveCurrentPrintOnSiblingNode(path, printed); + + return printed; + } + } + + if (options.semi && node?.extra?.['prevTemplatePrinted']) { + fixPreviousPrint( + node.extra['prevTemplatePrinted'] as AST.builders.Doc[], + path, + options, + print, + args, + ); + } + + return printer.print(path, options, print, args); +} + +export const printers: Record> = { + [PRINTER_NAME]: { + ...printer, + // @ts-expect-error: Type <...> is not assignable to <...> + embed, + getVisitorKeys, + print, + }, +}; diff --git a/src/printers/ambiguity.ts b/src/printers/ambiguity.ts new file mode 100644 index 00000000..9f456101 --- /dev/null +++ b/src/printers/ambiguity.ts @@ -0,0 +1,55 @@ +import type { AstPath, doc as AST, Printer } from 'prettier'; +import { printers as prettierPrinters } from 'prettier/plugins/estree'; + +import type { PluginOptions } from '../options.js'; +import { flattenDoc } from '../utils/doc.js'; +import type { NodeType } from '../utils/index.js'; + +const printer = prettierPrinters['estree'] as Printer; + +/** + * Search next non EmptyStatement node and set current print, so we can fix it + * later if its ambiguous + */ +export function saveCurrentPrintOnSiblingNode( + path: AstPath, + printed: AST.builders.Doc[], +): void { + const { index, siblings } = path; + + if (index === null) { + return; + } + + const nextNode = siblings + ?.slice(index + 1) + .find((n) => n?.type !== 'EmptyStatement'); + + if (nextNode) { + nextNode.extra = nextNode.extra || {}; + nextNode.extra['prevTemplatePrinted'] = printed; + } +} + +/** HACK to fix ASI semi-colons. */ +export function fixPreviousPrint( + previousTemplatePrinted: AST.builders.Doc[], + path: AstPath, + options: PluginOptions, + print: (path: AstPath) => AST.builders.Doc, + args: unknown, +): void { + const printedSemiFalse = printer.print( + path, + { ...options, semi: false }, + print, + args, + ); + + const flat = flattenDoc(printedSemiFalse); + const previousFlat = flattenDoc(previousTemplatePrinted); + + if (flat[0]?.startsWith(';') && previousFlat.at(-1) !== ';') { + previousTemplatePrinted.push(';'); + } +} diff --git a/src/printers/ignore.ts b/src/printers/ignore.ts new file mode 100644 index 00000000..a35fb222 --- /dev/null +++ b/src/printers/ignore.ts @@ -0,0 +1,18 @@ +import type { AstPath } from 'prettier'; + +import type { NodeType } from '../utils/index.js'; + +export function checkPrettierIgnore(path: AstPath): boolean { + if (hasPrettierIgnore(path)) { + return true; + } + + return ( + Boolean(path.getParentNode()) && + path.callParent((parent) => checkPrettierIgnore(parent)) + ); +} + +export function hasPrettierIgnore(path: AstPath): boolean { + return path.node?.leadingComments?.at(-1)?.value.trim() === 'prettier-ignore'; +} diff --git a/src/printers/index.ts b/src/printers/index.ts new file mode 100644 index 00000000..141f0be8 --- /dev/null +++ b/src/printers/index.ts @@ -0,0 +1,3 @@ +export * from './ambiguity.js'; +export * from './ignore.js'; +export * from './print.js'; diff --git a/src/printers/print.ts b/src/printers/print.ts new file mode 100644 index 00000000..0a7f3b2a --- /dev/null +++ b/src/printers/print.ts @@ -0,0 +1,102 @@ +import { type AstPath, doc as AST } from 'prettier'; + +import type { PluginOptions } from '../options.js'; +import { isGlimmerTemplate } from '../types/glimmer.js'; +import { assert } from '../utils/assert.js'; +import { flattenDoc } from '../utils/doc.js'; +import { + type NodeType, + TEMPLATE_TAG_CLOSE, + TEMPLATE_TAG_OPEN, +} from '../utils/index.js'; + +export function docMatchesString( + doc: AST.builders.Doc | undefined, + string: string, +): doc is string { + return typeof doc === 'string' && doc.trim() === string; +} + +export function printRawText( + { node }: AstPath, + options: PluginOptions, +): string { + if (!node) { + return ''; + } + + if (isGlimmerTemplate(node)) { + return ( + TEMPLATE_TAG_OPEN + node.extra.template.contents + TEMPLATE_TAG_CLOSE + ); + } + + assert('expected start', typeof node.start == 'number'); + assert('expected end', typeof node.end == 'number'); + + return options.originalText.slice(node.start, node.end); +} + +/** + * Returns a Prettier `Doc` for the given `TemplateLiteral` contents formatted + * using Prettier's built-in glimmer parser. + * + * NOTE: The contents are not surrounded with "`" + */ +export async function printTemplateContent( + text: string, + textToDoc: ( + text: string, + options: PluginOptions, + ) => Promise, + options: PluginOptions, +): Promise { + return await textToDoc(text.trim(), { + ...options, + parser: 'glimmer', + singleQuote: options.templateSingleQuote ?? options.singleQuote, + }); +} + +/** + * Prints the given template content as a template tag. + * + * If `useHardline` is `true`, will use Prettier's hardline builder to force + * each tag to print on a new line. + * + * If `useHardline` is `false`, will use Prettier's softline builder to allow + * the tags to print on the same line if they fit. + */ +export function printTemplateTag( + content: AST.builders.Doc, +): AST.builders.Doc[] { + const contents = flattenDoc(content); + const useHardline = contents.some( + (c) => + // contains angle bracket tag + /<.+>/.test(c) || + // contains hbs block + /{{~?#.+}}/.test(c), + ); + const line = useHardline ? AST.builders.hardline : AST.builders.softline; + + const doc = [ + TEMPLATE_TAG_OPEN, + AST.builders.indent([line, AST.builders.group(content)]), + line, + TEMPLATE_TAG_CLOSE, + ]; + + return [AST.builders.group(doc)]; +} + +/** Remove the empty strings that Prettier added so we can manage them. */ +export function trimPrinted(printed: AST.builders.Doc[]): void { + while (docMatchesString(printed[0], '')) { + printed.shift(); + } + + while (docMatchesString(printed.at(-1), '')) { + printed.pop(); + } +} diff --git a/src/utils/content-tag.ts b/src/utils/content-tag.ts index 6623444d..0b846753 100644 --- a/src/utils/content-tag.ts +++ b/src/utils/content-tag.ts @@ -1,4 +1,4 @@ -/* eslint-disable jsdoc/require-jsdoc, unicorn/prefer-export-from */ +/* eslint-disable unicorn/prefer-export-from */ import { type Parsed as ContentTag, Preprocessor, diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..bc5153f2 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,9 @@ +import type { Node } from '@babel/types'; + +export type NodeType = Node | undefined; + +export const PARSER_NAME = 'ember-template-tag'; +export const PRINTER_NAME = 'ember-template-tag-estree'; + +export const TEMPLATE_TAG_OPEN = ''; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2..00000000 --- a/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/tests/cases/gjs/complex.gjs b/tests-vitest/cases/gjs/complex.gjs similarity index 100% rename from tests/cases/gjs/complex.gjs rename to tests-vitest/cases/gjs/complex.gjs diff --git a/tests/cases/gjs/component-class-with-content-before-template.gjs b/tests-vitest/cases/gjs/component-class-with-content-before-template.gjs similarity index 100% rename from tests/cases/gjs/component-class-with-content-before-template.gjs rename to tests-vitest/cases/gjs/component-class-with-content-before-template.gjs diff --git a/tests/cases/gjs/component-class-with-template-literal.gjs b/tests-vitest/cases/gjs/component-class-with-template-literal.gjs similarity index 100% rename from tests/cases/gjs/component-class-with-template-literal.gjs rename to tests-vitest/cases/gjs/component-class-with-template-literal.gjs diff --git a/tests/cases/gjs/component-class.gjs b/tests-vitest/cases/gjs/component-class.gjs similarity index 100% rename from tests/cases/gjs/component-class.gjs rename to tests-vitest/cases/gjs/component-class.gjs diff --git a/tests/cases/gjs/default-export.gjs b/tests-vitest/cases/gjs/default-export.gjs similarity index 100% rename from tests/cases/gjs/default-export.gjs rename to tests-vitest/cases/gjs/default-export.gjs diff --git a/tests/cases/gjs/exported-mod-var.gjs b/tests-vitest/cases/gjs/exported-mod-var.gjs similarity index 100% rename from tests/cases/gjs/exported-mod-var.gjs rename to tests-vitest/cases/gjs/exported-mod-var.gjs diff --git a/tests/cases/gjs/invalid-template.gjs b/tests-vitest/cases/gjs/invalid-template.gjs similarity index 100% rename from tests/cases/gjs/invalid-template.gjs rename to tests-vitest/cases/gjs/invalid-template.gjs diff --git a/tests/cases/gjs/js-only.gjs b/tests-vitest/cases/gjs/js-only.gjs similarity index 100% rename from tests/cases/gjs/js-only.gjs rename to tests-vitest/cases/gjs/js-only.gjs diff --git a/tests/cases/gjs/mod-var.gjs b/tests-vitest/cases/gjs/mod-var.gjs similarity index 100% rename from tests/cases/gjs/mod-var.gjs rename to tests-vitest/cases/gjs/mod-var.gjs diff --git a/tests/cases/gjs/multiple-declarations.gjs b/tests-vitest/cases/gjs/multiple-declarations.gjs similarity index 100% rename from tests/cases/gjs/multiple-declarations.gjs rename to tests-vitest/cases/gjs/multiple-declarations.gjs diff --git a/tests/cases/gjs/one-line.gjs b/tests-vitest/cases/gjs/one-line.gjs similarity index 100% rename from tests/cases/gjs/one-line.gjs rename to tests-vitest/cases/gjs/one-line.gjs diff --git a/tests/cases/gjs/preceded-by-object.gjs b/tests-vitest/cases/gjs/preceded-by-object.gjs similarity index 100% rename from tests/cases/gjs/preceded-by-object.gjs rename to tests-vitest/cases/gjs/preceded-by-object.gjs diff --git a/tests/cases/gjs/prettier-ignore/component-class-with-template-literal.gjs b/tests-vitest/cases/gjs/prettier-ignore/component-class-with-template-literal.gjs similarity index 100% rename from tests/cases/gjs/prettier-ignore/component-class-with-template-literal.gjs rename to tests-vitest/cases/gjs/prettier-ignore/component-class-with-template-literal.gjs diff --git a/tests/cases/gjs/prettier-ignore/component-class.gjs b/tests-vitest/cases/gjs/prettier-ignore/component-class.gjs similarity index 100% rename from tests/cases/gjs/prettier-ignore/component-class.gjs rename to tests-vitest/cases/gjs/prettier-ignore/component-class.gjs diff --git a/tests/cases/gjs/prettier-ignore/default-export.gjs b/tests-vitest/cases/gjs/prettier-ignore/default-export.gjs similarity index 100% rename from tests/cases/gjs/prettier-ignore/default-export.gjs rename to tests-vitest/cases/gjs/prettier-ignore/default-export.gjs diff --git a/tests/cases/gjs/prettier-ignore/exported-mod-var.gjs b/tests-vitest/cases/gjs/prettier-ignore/exported-mod-var.gjs similarity index 100% rename from tests/cases/gjs/prettier-ignore/exported-mod-var.gjs rename to tests-vitest/cases/gjs/prettier-ignore/exported-mod-var.gjs diff --git a/tests/cases/gjs/prettier-ignore/js-only.gjs b/tests-vitest/cases/gjs/prettier-ignore/js-only.gjs similarity index 100% rename from tests/cases/gjs/prettier-ignore/js-only.gjs rename to tests-vitest/cases/gjs/prettier-ignore/js-only.gjs diff --git a/tests/cases/gjs/prettier-ignore/multiple-declarations.gjs b/tests-vitest/cases/gjs/prettier-ignore/multiple-declarations.gjs similarity index 100% rename from tests/cases/gjs/prettier-ignore/multiple-declarations.gjs rename to tests-vitest/cases/gjs/prettier-ignore/multiple-declarations.gjs diff --git a/tests/cases/gjs/prettier-ignore/one-line.gjs b/tests-vitest/cases/gjs/prettier-ignore/one-line.gjs similarity index 100% rename from tests/cases/gjs/prettier-ignore/one-line.gjs rename to tests-vitest/cases/gjs/prettier-ignore/one-line.gjs diff --git a/tests/cases/gjs/prettier-ignore/simple.gjs b/tests-vitest/cases/gjs/prettier-ignore/simple.gjs similarity index 100% rename from tests/cases/gjs/prettier-ignore/simple.gjs rename to tests-vitest/cases/gjs/prettier-ignore/simple.gjs diff --git a/tests/cases/gjs/route.gjs b/tests-vitest/cases/gjs/route.gjs similarity index 100% rename from tests/cases/gjs/route.gjs rename to tests-vitest/cases/gjs/route.gjs diff --git a/tests/cases/gjs/simple-with-tag.gjs b/tests-vitest/cases/gjs/simple-with-tag.gjs similarity index 100% rename from tests/cases/gjs/simple-with-tag.gjs rename to tests-vitest/cases/gjs/simple-with-tag.gjs diff --git a/tests/cases/gjs/simple.gjs b/tests-vitest/cases/gjs/simple.gjs similarity index 100% rename from tests/cases/gjs/simple.gjs rename to tests-vitest/cases/gjs/simple.gjs diff --git a/tests/cases/gts/complex.gts b/tests-vitest/cases/gts/complex.gts similarity index 100% rename from tests/cases/gts/complex.gts rename to tests-vitest/cases/gts/complex.gts diff --git a/tests/cases/gts/component-class-with-template-literal.gts b/tests-vitest/cases/gts/component-class-with-template-literal.gts similarity index 100% rename from tests/cases/gts/component-class-with-template-literal.gts rename to tests-vitest/cases/gts/component-class-with-template-literal.gts diff --git a/tests/cases/gts/component-class.gts b/tests-vitest/cases/gts/component-class.gts similarity index 100% rename from tests/cases/gts/component-class.gts rename to tests-vitest/cases/gts/component-class.gts diff --git a/tests/cases/gts/default-export.gts b/tests-vitest/cases/gts/default-export.gts similarity index 100% rename from tests/cases/gts/default-export.gts rename to tests-vitest/cases/gts/default-export.gts diff --git a/tests/cases/gts/exported-mod-var-with-as.gts b/tests-vitest/cases/gts/exported-mod-var-with-as.gts similarity index 100% rename from tests/cases/gts/exported-mod-var-with-as.gts rename to tests-vitest/cases/gts/exported-mod-var-with-as.gts diff --git a/tests/cases/gts/exported-mod-var.gts b/tests-vitest/cases/gts/exported-mod-var.gts similarity index 100% rename from tests/cases/gts/exported-mod-var.gts rename to tests-vitest/cases/gts/exported-mod-var.gts diff --git a/tests/cases/gts/implied-export-default-satisfies.gts b/tests-vitest/cases/gts/implied-export-default-satisfies.gts similarity index 100% rename from tests/cases/gts/implied-export-default-satisfies.gts rename to tests-vitest/cases/gts/implied-export-default-satisfies.gts diff --git a/tests/cases/gts/issue-191-b.gts b/tests-vitest/cases/gts/issue-191-b.gts similarity index 100% rename from tests/cases/gts/issue-191-b.gts rename to tests-vitest/cases/gts/issue-191-b.gts diff --git a/tests/cases/gts/issue-191-c.gts b/tests-vitest/cases/gts/issue-191-c.gts similarity index 100% rename from tests/cases/gts/issue-191-c.gts rename to tests-vitest/cases/gts/issue-191-c.gts diff --git a/tests/cases/gts/issue-191-d.gts b/tests-vitest/cases/gts/issue-191-d.gts similarity index 100% rename from tests/cases/gts/issue-191-d.gts rename to tests-vitest/cases/gts/issue-191-d.gts diff --git a/tests/cases/gts/issue-191-e.gts b/tests-vitest/cases/gts/issue-191-e.gts similarity index 100% rename from tests/cases/gts/issue-191-e.gts rename to tests-vitest/cases/gts/issue-191-e.gts diff --git a/tests/cases/gts/issue-191.gts b/tests-vitest/cases/gts/issue-191.gts similarity index 100% rename from tests/cases/gts/issue-191.gts rename to tests-vitest/cases/gts/issue-191.gts diff --git a/tests/cases/gts/issue-255.gts b/tests-vitest/cases/gts/issue-255.gts similarity index 100% rename from tests/cases/gts/issue-255.gts rename to tests-vitest/cases/gts/issue-255.gts diff --git a/tests/cases/gts/js-only.gts b/tests-vitest/cases/gts/js-only.gts similarity index 100% rename from tests/cases/gts/js-only.gts rename to tests-vitest/cases/gts/js-only.gts diff --git a/tests/cases/gts/mod-var-with-as.gts b/tests-vitest/cases/gts/mod-var-with-as.gts similarity index 100% rename from tests/cases/gts/mod-var-with-as.gts rename to tests-vitest/cases/gts/mod-var-with-as.gts diff --git a/tests/cases/gts/mod-var.gts b/tests-vitest/cases/gts/mod-var.gts similarity index 100% rename from tests/cases/gts/mod-var.gts rename to tests-vitest/cases/gts/mod-var.gts diff --git a/tests/cases/gts/multiple-declarations-with-as.gts b/tests-vitest/cases/gts/multiple-declarations-with-as.gts similarity index 100% rename from tests/cases/gts/multiple-declarations-with-as.gts rename to tests-vitest/cases/gts/multiple-declarations-with-as.gts diff --git a/tests/cases/gts/multiple-declarations.gts b/tests-vitest/cases/gts/multiple-declarations.gts similarity index 100% rename from tests/cases/gts/multiple-declarations.gts rename to tests-vitest/cases/gts/multiple-declarations.gts diff --git a/tests/cases/gts/one-line.gts b/tests-vitest/cases/gts/one-line.gts similarity index 100% rename from tests/cases/gts/one-line.gts rename to tests-vitest/cases/gts/one-line.gts diff --git a/tests/cases/gts/prettier-ignore/component-class-with-template-literal.gts b/tests-vitest/cases/gts/prettier-ignore/component-class-with-template-literal.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/component-class-with-template-literal.gts rename to tests-vitest/cases/gts/prettier-ignore/component-class-with-template-literal.gts diff --git a/tests/cases/gts/prettier-ignore/component-class.gts b/tests-vitest/cases/gts/prettier-ignore/component-class.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/component-class.gts rename to tests-vitest/cases/gts/prettier-ignore/component-class.gts diff --git a/tests/cases/gts/prettier-ignore/default-export.gts b/tests-vitest/cases/gts/prettier-ignore/default-export.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/default-export.gts rename to tests-vitest/cases/gts/prettier-ignore/default-export.gts diff --git a/tests/cases/gts/prettier-ignore/exported-mod-var-with-as.gts b/tests-vitest/cases/gts/prettier-ignore/exported-mod-var-with-as.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/exported-mod-var-with-as.gts rename to tests-vitest/cases/gts/prettier-ignore/exported-mod-var-with-as.gts diff --git a/tests/cases/gts/prettier-ignore/exported-mod-var.gts b/tests-vitest/cases/gts/prettier-ignore/exported-mod-var.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/exported-mod-var.gts rename to tests-vitest/cases/gts/prettier-ignore/exported-mod-var.gts diff --git a/tests/cases/gts/prettier-ignore/js-only.gts b/tests-vitest/cases/gts/prettier-ignore/js-only.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/js-only.gts rename to tests-vitest/cases/gts/prettier-ignore/js-only.gts diff --git a/tests/cases/gts/prettier-ignore/multiple-declarations-with-as.gts b/tests-vitest/cases/gts/prettier-ignore/multiple-declarations-with-as.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/multiple-declarations-with-as.gts rename to tests-vitest/cases/gts/prettier-ignore/multiple-declarations-with-as.gts diff --git a/tests/cases/gts/prettier-ignore/multiple-declarations.gts b/tests-vitest/cases/gts/prettier-ignore/multiple-declarations.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/multiple-declarations.gts rename to tests-vitest/cases/gts/prettier-ignore/multiple-declarations.gts diff --git a/tests/cases/gts/prettier-ignore/one-line.gts b/tests-vitest/cases/gts/prettier-ignore/one-line.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/one-line.gts rename to tests-vitest/cases/gts/prettier-ignore/one-line.gts diff --git a/tests/cases/gts/prettier-ignore/simple.gts b/tests-vitest/cases/gts/prettier-ignore/simple.gts similarity index 100% rename from tests/cases/gts/prettier-ignore/simple.gts rename to tests-vitest/cases/gts/prettier-ignore/simple.gts diff --git a/tests/cases/gts/rendering-test.gts b/tests-vitest/cases/gts/rendering-test.gts similarity index 100% rename from tests/cases/gts/rendering-test.gts rename to tests-vitest/cases/gts/rendering-test.gts diff --git a/tests/cases/gts/simple.gts b/tests-vitest/cases/gts/simple.gts similarity index 100% rename from tests/cases/gts/simple.gts rename to tests-vitest/cases/gts/simple.gts diff --git a/tests/helpers/ambiguous.ts b/tests-vitest/helpers/ambiguous.ts similarity index 97% rename from tests/helpers/ambiguous.ts rename to tests-vitest/helpers/ambiguous.ts index 84a4f21d..b4a961d2 100644 --- a/tests/helpers/ambiguous.ts +++ b/tests-vitest/helpers/ambiguous.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; -import type { Options } from '../../src/options.js'; +import type { PluginOptions } from '../../src/options.js'; import { parse } from '../../src/utils/content-tag.js'; import type { TestCase } from '../helpers/cases.js'; import { getAllCases } from '../helpers/cases.js'; @@ -89,7 +89,7 @@ export function makeAmbiguousExpressionTest( async function behavesLikeFormattedAmbiguousCase( code: string, - formatOptions: Partial = {}, + formatOptions: Partial = {}, ): Promise { try { const result = await format(code, formatOptions); diff --git a/tests/helpers/cases.ts b/tests-vitest/helpers/cases.ts similarity index 100% rename from tests/helpers/cases.ts rename to tests-vitest/helpers/cases.ts diff --git a/tests/helpers/format.ts b/tests-vitest/helpers/format.ts similarity index 57% rename from tests/helpers/format.ts rename to tests-vitest/helpers/format.ts index f15b2309..a45cb8fd 100644 --- a/tests/helpers/format.ts +++ b/tests-vitest/helpers/format.ts @@ -1,13 +1,14 @@ import type { Plugin } from 'prettier'; import { format as prettierFormat } from 'prettier'; -import { PARSER_NAME } from '../../src/config.js'; -import plugin from '../../src/main.js'; -import type { Options } from '../../src/options.js'; +import { languages, options, parsers, printers } from '../../src/main.js'; +import type { PluginOptions } from '../../src/options.js'; -const DEFAULT_OPTIONS: Partial = { - parser: PARSER_NAME, - plugins: [plugin as Plugin], +const plugin: Plugin = { + languages, + options, + parsers, + printers, }; /** @@ -18,10 +19,11 @@ const DEFAULT_OPTIONS: Partial = { */ export async function format( code: string, - overrides: Partial = {}, + overrides: Partial = {}, ): Promise { return await prettierFormat(code, { - ...DEFAULT_OPTIONS, ...overrides, + parser: 'ember-template-tag', + plugins: [plugin], }); } diff --git a/tests/helpers/make-suite.ts b/tests-vitest/helpers/make-suite.ts similarity index 94% rename from tests/helpers/make-suite.ts rename to tests-vitest/helpers/make-suite.ts index 082a714d..29a1d52d 100644 --- a/tests/helpers/make-suite.ts +++ b/tests-vitest/helpers/make-suite.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; -import type { Options } from '../../src/options.js'; +import type { PluginOptions } from '../../src/options.js'; import { AMBIGUOUS_PLACEHOLDER, getAmbiguousCases, @@ -12,7 +12,7 @@ import { format } from './format.js'; export interface Config { name: string; - options?: Partial; + options?: Partial; } /** diff --git a/tests/package.json b/tests-vitest/package.json similarity index 100% rename from tests/package.json rename to tests-vitest/package.json diff --git a/tests/pnpm-lock.yaml b/tests-vitest/pnpm-lock.yaml similarity index 100% rename from tests/pnpm-lock.yaml rename to tests-vitest/pnpm-lock.yaml diff --git a/tests-vitest/tsconfig.json b/tests-vitest/tsconfig.json new file mode 100644 index 00000000..69c55d87 --- /dev/null +++ b/tests-vitest/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.build.json", + "include": ["."] +} diff --git a/tests/unit-tests/__snapshots__/format.test.ts.snap b/tests-vitest/unit-tests/__snapshots__/format.test.ts.snap similarity index 100% rename from tests/unit-tests/__snapshots__/format.test.ts.snap rename to tests-vitest/unit-tests/__snapshots__/format.test.ts.snap diff --git a/tests/unit-tests/ambiguous/__snapshots__/arrow-parens-avoid.test.ts.snap b/tests-vitest/unit-tests/ambiguous/__snapshots__/arrow-parens-avoid.test.ts.snap similarity index 100% rename from tests/unit-tests/ambiguous/__snapshots__/arrow-parens-avoid.test.ts.snap rename to tests-vitest/unit-tests/ambiguous/__snapshots__/arrow-parens-avoid.test.ts.snap diff --git a/tests/unit-tests/ambiguous/__snapshots__/index.test.ts.snap b/tests-vitest/unit-tests/ambiguous/__snapshots__/index.test.ts.snap similarity index 100% rename from tests/unit-tests/ambiguous/__snapshots__/index.test.ts.snap rename to tests-vitest/unit-tests/ambiguous/__snapshots__/index.test.ts.snap diff --git a/tests/unit-tests/ambiguous/__snapshots__/semi-false.test.ts.snap b/tests-vitest/unit-tests/ambiguous/__snapshots__/semi-false.test.ts.snap similarity index 100% rename from tests/unit-tests/ambiguous/__snapshots__/semi-false.test.ts.snap rename to tests-vitest/unit-tests/ambiguous/__snapshots__/semi-false.test.ts.snap diff --git a/tests/unit-tests/ambiguous/arrow-parens-avoid.test.ts b/tests-vitest/unit-tests/ambiguous/arrow-parens-avoid.test.ts similarity index 100% rename from tests/unit-tests/ambiguous/arrow-parens-avoid.test.ts rename to tests-vitest/unit-tests/ambiguous/arrow-parens-avoid.test.ts diff --git a/tests/unit-tests/ambiguous/index.test.ts b/tests-vitest/unit-tests/ambiguous/index.test.ts similarity index 100% rename from tests/unit-tests/ambiguous/index.test.ts rename to tests-vitest/unit-tests/ambiguous/index.test.ts diff --git a/tests/unit-tests/ambiguous/semi-false.test.ts b/tests-vitest/unit-tests/ambiguous/semi-false.test.ts similarity index 100% rename from tests/unit-tests/ambiguous/semi-false.test.ts rename to tests-vitest/unit-tests/ambiguous/semi-false.test.ts diff --git a/tests/unit-tests/config/__snapshots__/semi-false.test.ts.snap b/tests-vitest/unit-tests/config/__snapshots__/semi-false.test.ts.snap similarity index 100% rename from tests/unit-tests/config/__snapshots__/semi-false.test.ts.snap rename to tests-vitest/unit-tests/config/__snapshots__/semi-false.test.ts.snap diff --git a/tests/unit-tests/config/__snapshots__/template-export-default.test.ts.snap b/tests-vitest/unit-tests/config/__snapshots__/template-export-default.test.ts.snap similarity index 100% rename from tests/unit-tests/config/__snapshots__/template-export-default.test.ts.snap rename to tests-vitest/unit-tests/config/__snapshots__/template-export-default.test.ts.snap diff --git a/tests/unit-tests/config/__snapshots__/template-single-quote.test.ts.snap b/tests-vitest/unit-tests/config/__snapshots__/template-single-quote.test.ts.snap similarity index 100% rename from tests/unit-tests/config/__snapshots__/template-single-quote.test.ts.snap rename to tests-vitest/unit-tests/config/__snapshots__/template-single-quote.test.ts.snap diff --git a/tests/unit-tests/config/quote-cases/component-class.gjs b/tests-vitest/unit-tests/config/quote-cases/component-class.gjs similarity index 100% rename from tests/unit-tests/config/quote-cases/component-class.gjs rename to tests-vitest/unit-tests/config/quote-cases/component-class.gjs diff --git a/tests/unit-tests/config/semi-false.test.ts b/tests-vitest/unit-tests/config/semi-false.test.ts similarity index 100% rename from tests/unit-tests/config/semi-false.test.ts rename to tests-vitest/unit-tests/config/semi-false.test.ts diff --git a/tests/unit-tests/config/template-export-default.test.ts b/tests-vitest/unit-tests/config/template-export-default.test.ts similarity index 100% rename from tests/unit-tests/config/template-export-default.test.ts rename to tests-vitest/unit-tests/config/template-export-default.test.ts diff --git a/tests/unit-tests/config/template-single-quote.test.ts b/tests-vitest/unit-tests/config/template-single-quote.test.ts similarity index 100% rename from tests/unit-tests/config/template-single-quote.test.ts rename to tests-vitest/unit-tests/config/template-single-quote.test.ts diff --git a/tests/unit-tests/format.test.ts b/tests-vitest/unit-tests/format.test.ts similarity index 100% rename from tests/unit-tests/format.test.ts rename to tests-vitest/unit-tests/format.test.ts diff --git a/tests/unit-tests/preprocess.test.ts b/tests-vitest/unit-tests/preprocess.test.ts similarity index 97% rename from tests/unit-tests/preprocess.test.ts rename to tests-vitest/unit-tests/preprocess.test.ts index f516c030..0c28aa3e 100644 --- a/tests/unit-tests/preprocess.test.ts +++ b/tests-vitest/unit-tests/preprocess.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from 'vitest'; import { codeToGlimmerAst, preprocessTemplateRange, -} from '../../src/parse/preprocess.js'; +} from '../../src/parsers/preprocess.js'; const TEST_CASES = [ { diff --git a/tests/fixtures/example/index.ts b/tests/fixtures/example/index.ts new file mode 100644 index 00000000..9b22c259 --- /dev/null +++ b/tests/fixtures/example/index.ts @@ -0,0 +1,6 @@ +import { convertFixtureToJson } from '@codemod-utils/tests'; + +const inputProject = convertFixtureToJson('example/input'); +const outputProject = convertFixtureToJson('example/output'); + +export { inputProject, outputProject }; diff --git a/examples/input/example.gjs b/tests/fixtures/example/input/example-1.gjs similarity index 100% rename from examples/input/example.gjs rename to tests/fixtures/example/input/example-1.gjs diff --git a/examples/input/example.gts b/tests/fixtures/example/input/example-2.gts similarity index 100% rename from examples/input/example.gts rename to tests/fixtures/example/input/example-2.gts diff --git a/examples/expected-output/example.gjs b/tests/fixtures/example/output/example-1.gjs similarity index 100% rename from examples/expected-output/example.gjs rename to tests/fixtures/example/output/example-1.gjs diff --git a/examples/expected-output/example.gts b/tests/fixtures/example/output/example-2.gts similarity index 100% rename from examples/expected-output/example.gts rename to tests/fixtures/example/output/example-2.gts diff --git a/tests/helpers/format-file.ts b/tests/helpers/format-file.ts new file mode 100644 index 00000000..1057fb35 --- /dev/null +++ b/tests/helpers/format-file.ts @@ -0,0 +1,22 @@ +import { format } from 'prettier'; + +import type { PluginOptions } from '../../src/options.js'; +import type { NodeType } from '../../src/utils/index.js'; + +const defaultOptions = { + printWidth: 80, + singleQuote: true, + templateSingleQuote: false, +}; + +export async function formatFile( + file: string, + pluginOptions?: Partial>, +): Promise { + return await format(file, { + ...defaultOptions, + ...pluginOptions, + parser: 'ember-template-tag', + plugins: ['./dist-for-testing/src/main.js'], + }); +} diff --git a/tests/helpers/index.ts b/tests/helpers/index.ts new file mode 100644 index 00000000..c669855c --- /dev/null +++ b/tests/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './format-file.js'; +export * from './run-prettier.js'; diff --git a/tests/helpers/run-prettier.ts b/tests/helpers/run-prettier.ts new file mode 100644 index 00000000..75bfb734 --- /dev/null +++ b/tests/helpers/run-prettier.ts @@ -0,0 +1,46 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +// eslint-disable-next-line unicorn/import-style +import { join } from 'node:path'; + +import { findFiles } from '@codemod-utils/files'; +import { assertFixture, loadFixture } from '@codemod-utils/tests'; + +import type { PluginOptions } from '../../src/options.js'; +import { formatFile } from './format-file.js'; + +type DirectoryJSON = Parameters[0]; + +type Options = { + fixturePath: string; + pluginOptions?: Partial; +}; + +export async function runPrettier(options: Options): Promise { + const { fixturePath, pluginOptions } = options; + + const { inputProject, outputProject } = (await import( + join('../fixtures', fixturePath, 'index.js') + )) as { + inputProject: DirectoryJSON; + outputProject: DirectoryJSON; + }; + + const projectRoot = join('tmp', fixturePath); + + loadFixture(inputProject, { projectRoot }); + + const filePaths = findFiles('**/*.{gjs,gts}', { + projectRoot, + }); + + await Promise.all( + filePaths.map(async (filePath) => { + const oldFile = readFileSync(join(projectRoot, filePath), 'utf8'); + const newFile = await formatFile(oldFile, pluginOptions); + + writeFileSync(join(projectRoot, filePath), newFile, 'utf8'); + }), + ); + + assertFixture(outputProject, { projectRoot }); +} diff --git a/tests/main/example.test.ts b/tests/main/example.test.ts new file mode 100644 index 00000000..03884778 --- /dev/null +++ b/tests/main/example.test.ts @@ -0,0 +1,14 @@ +import { test } from '@codemod-utils/tests'; + +import { runPrettier } from '../helpers/index.js'; + +test('main > example', async function () { + await runPrettier({ + fixturePath: 'example', + }); + + // Check idempotence + await runPrettier({ + fixturePath: 'example', + }); +}); diff --git a/tests/tsconfig.json b/tests/tsconfig.json deleted file mode 100644 index cd05cad1..00000000 --- a/tests/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "include": ["."], - "extends": "../tsconfig.json" -} diff --git a/tmp/example/example-1.gjs b/tmp/example/example-1.gjs new file mode 100644 index 00000000..db3f200e --- /dev/null +++ b/tmp/example/example-1.gjs @@ -0,0 +1,13 @@ +import Component from '@glimmer/component'; + +/** + * An example GJS file on which we can run the Prettier for GJS plugin. + */ +class MyComponent extends Component { + +} diff --git a/tmp/example/example-2.gts b/tmp/example/example-2.gts new file mode 100644 index 00000000..12fac0ff --- /dev/null +++ b/tmp/example/example-2.gts @@ -0,0 +1,14 @@ +import type { TemplateOnlyComponent } from '@ember/component/template-only'; + +export interface Signature { + Element: HTMLElement; + Args: {}; + Yields: []; +} + + as TemplateOnlyComponent diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..eb9529c7 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": ["@tsconfig/node18/tsconfig", "@tsconfig/strictest/tsconfig"], + "compilerOptions": { + "declaration": false, + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "dist/src", + "verbatimModuleSyntax": true + }, + "include": ["src", "types"], + "exclude": [] +} diff --git a/tsconfig.json b/tsconfig.json index 51a4cf1e..ee0787db 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,12 @@ { - "extends": [ - "@tsconfig/strictest/tsconfig.json", - "@tsconfig/node18/tsconfig.json" - ], + "extends": ["@tsconfig/node18/tsconfig", "@tsconfig/strictest/tsconfig"], "compilerOptions": { - "useDefineForClassFields": true, - "sourceMap": true, - "resolveJsonModule": true, - "isolatedModules": true, - "verbatimModuleSyntax": true, - "noEmit": true, - - "paths": { - "*": ["./@types/*"] - } + "declaration": false, + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "dist-for-testing", + "verbatimModuleSyntax": true }, - "include": ["src"] + "include": ["src", "tests", "types"], + "exclude": [] } diff --git a/tsconfig.lint.json b/tsconfig.lint.json deleted file mode 100644 index 2c2643d4..00000000 --- a/tsconfig.lint.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": true, - "allowJs": true - }, - "include": ["src", "tests", "vite.config.ts"] -} diff --git a/types/prettier.d.ts b/types/prettier.d.ts new file mode 100644 index 00000000..d9d99ab6 --- /dev/null +++ b/types/prettier.d.ts @@ -0,0 +1,7 @@ +import type { Printer } from 'prettier'; + +declare module 'prettier/plugins/estree' { + export const printers: { + estree: Printer; + }; +} diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index 8bba1e68..00000000 --- a/vite.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vite'; - -export default defineConfig({ - build: { - rollupOptions: { - external: ['content-tag'], - }, - lib: { - entry: 'src/main.ts', - formats: ['es'], - }, - minify: false, - }, -});