diff --git a/.vscode/settings.json b/.vscode/settings.json index b9c890abfa..7ebe6ec0ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -39,7 +39,8 @@ "json", "jsonc", "yaml", - "toml" + "toml", + "xml" ], "pair-diff.patterns": [ diff --git a/README.md b/README.md index 7b7a5a93e7..907b40a30f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - Opinionated, but [very customizable](#customization) - [ESLint Flat config](https://eslint.org/docs/latest/use/configure/configuration-files-new), compose easily! - Optional [React](#react), [Svelte](#svelte), [UnoCSS](#unocss), [Astro](#astro), [Solid](#solid) support -- Optional [formatters](#formatters) support for formatting CSS, HTML, etc. +- Optional [formatters](#formatters) support for formatting CSS, HTML, XML, etc. - **Style principle**: Minimal for reading, stable for diff, consistent - Sorted imports, dangling commas - Single quotes, no semi @@ -143,6 +143,7 @@ Add the following settings to your `.vscode/settings.json`: "jsonc", "yaml", "toml", + "xml", "gql", "graphql", "astro" diff --git a/fixtures/input/xml.xml b/fixtures/input/xml.xml new file mode 100644 index 0000000000..8fbc57b9b3 --- /dev/null +++ b/fixtures/input/xml.xml @@ -0,0 +1,9 @@ + +Effective Java45.00 + Bluetooth Speaker120.00 + Clean Code + 33.50 + + + + diff --git a/fixtures/output/all/jsx.jsx b/fixtures/output/all/jsx.jsx index 17392c70b3..4f287e4567 100644 --- a/fixtures/output/all/jsx.jsx +++ b/fixtures/output/all/jsx.jsx @@ -9,7 +9,7 @@ export function HelloWorld({ // TODO: Don't use random in render const num = Math.floor (Math.random() * 1e+7).toString() - .replace(/\.\d+/ig, '') + .replace(/\.\d+/g, '') return (
diff --git a/fixtures/output/no-markdown-with-formatters/jsx.jsx b/fixtures/output/no-markdown-with-formatters/jsx.jsx index 1a5b5934d3..e54bb98d4f 100644 --- a/fixtures/output/no-markdown-with-formatters/jsx.jsx +++ b/fixtures/output/no-markdown-with-formatters/jsx.jsx @@ -9,7 +9,7 @@ export function HelloWorld({ // TODO: Don't use random in render const num = Math.floor (Math.random() * 1e+7).toString() - .replace(/\.\d+/ig, '') + .replace(/\.\d+/g, '') return
{ greeting.slice(0, 1).toUpperCase() + greeting.slice(1).toLowerCase() } diff --git a/fixtures/output/no-style/jsx.jsx b/fixtures/output/no-style/jsx.jsx index ccf14858c6..ce5c0332a0 100644 --- a/fixtures/output/no-style/jsx.jsx +++ b/fixtures/output/no-style/jsx.jsx @@ -6,7 +6,7 @@ export function HelloWorld({ // TODO: Don't use random in render const num = Math.floor (Math.random() * 1e+7).toString() - .replace(/\.\d+/ig, "") + .replace(/\.\d+/g, "") return
{ greeting.slice( 0, 1 ).toUpperCase() + greeting.slice(1).toLowerCase() } diff --git a/fixtures/output/tab-double-quotes/jsx.jsx b/fixtures/output/tab-double-quotes/jsx.jsx index 64b2d6f0e3..f574fa2734 100644 --- a/fixtures/output/tab-double-quotes/jsx.jsx +++ b/fixtures/output/tab-double-quotes/jsx.jsx @@ -9,7 +9,7 @@ export function HelloWorld({ // TODO: Don't use random in render const num = Math.floor (Math.random() * 1e+7).toString() - .replace(/\.\d+/ig, "") + .replace(/\.\d+/g, "") return (
diff --git a/fixtures/output/ts-override/jsx.jsx b/fixtures/output/ts-override/jsx.jsx index 17392c70b3..4f287e4567 100644 --- a/fixtures/output/ts-override/jsx.jsx +++ b/fixtures/output/ts-override/jsx.jsx @@ -9,7 +9,7 @@ export function HelloWorld({ // TODO: Don't use random in render const num = Math.floor (Math.random() * 1e+7).toString() - .replace(/\.\d+/ig, '') + .replace(/\.\d+/g, '') return (
diff --git a/fixtures/output/with-formatters/jsx.jsx b/fixtures/output/with-formatters/jsx.jsx index 17392c70b3..4f287e4567 100644 --- a/fixtures/output/with-formatters/jsx.jsx +++ b/fixtures/output/with-formatters/jsx.jsx @@ -9,7 +9,7 @@ export function HelloWorld({ // TODO: Don't use random in render const num = Math.floor (Math.random() * 1e+7).toString() - .replace(/\.\d+/ig, '') + .replace(/\.\d+/g, '') return (
diff --git a/fixtures/output/with-formatters/xml.xml b/fixtures/output/with-formatters/xml.xml new file mode 100644 index 0000000000..88571e3d82 --- /dev/null +++ b/fixtures/output/with-formatters/xml.xml @@ -0,0 +1,20 @@ + + + Effective Java + 45.00 + + + Bluetooth Speaker + 120.00 + + + Clean Code + 33.50 + + + + + + + + diff --git a/package.json b/package.json index b56a5e9d81..57e81bfaab 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "peerDependencies": { "@eslint-react/eslint-plugin": "^1.5.8", + "@prettier/plugin-xml": "^3.4.1", "@unocss/eslint-plugin": ">=0.50.0", "astro-eslint-parser": "^0.16.3", "eslint": ">=8.40.0", @@ -57,6 +58,9 @@ "@eslint-react/eslint-plugin": { "optional": true }, + "@prettier/plugin-xml": { + "optional": true + }, "@unocss/eslint-plugin": { "optional": true }, @@ -133,6 +137,7 @@ "@antfu/ni": "^0.21.12", "@eslint-react/eslint-plugin": "^1.5.11", "@eslint/config-inspector": "^0.4.8", + "@prettier/plugin-xml": "^3.4.1", "@stylistic/eslint-plugin-migrate": "^2.1.0", "@types/eslint": "^8.56.10", "@types/fs-extra": "^11.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85207a23c3..a3943bec2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,9 @@ importers: '@eslint/config-inspector': specifier: ^0.4.8 version: 0.4.8(eslint-ts-patch@9.2.0-6) + '@prettier/plugin-xml': + specifier: ^3.4.1 + version: 3.4.1(prettier@3.2.5) '@stylistic/eslint-plugin-migrate': specifier: ^2.1.0 version: 2.1.0(eslint-ts-patch@9.2.0-6)(typescript@5.4.5) @@ -712,6 +715,11 @@ packages: resolution: {integrity: sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@prettier/plugin-xml@3.4.1': + resolution: {integrity: sha512-Uf/6/+9ez6z/IvZErgobZ2G9n1ybxF5BhCd7eMcKqfoWuOzzNUxBipNo3QAP8kRC1VD18TIo84no7LhqtyDcTg==} + peerDependencies: + prettier: ^3.0.0 + '@rollup/rollup-android-arm-eabi@4.4.1': resolution: {integrity: sha512-Ss4suS/sd+6xLRu+MLCkED2mUrAyqHmmvZB+zpzZ9Znn9S8wCkTQCJaQ8P8aHofnvG5L16u9MVnJjCqioPErwQ==} cpu: [arm] @@ -1015,6 +1023,9 @@ packages: '@vue/shared@3.4.27': resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} + '@xml-tools/parser@1.0.11': + resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1186,6 +1197,9 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + chevrotain@7.1.1: + resolution: {integrity: sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -1912,8 +1926,8 @@ packages: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - importx@0.2.0: - resolution: {integrity: sha512-a1WsQz4efNJGPPaQkcMioCQ1ttRZlJh2e53ivJUuR/LvjYW0cCqe2m6gcBqj3U+QyG7KU5zLkkZ2CtObNKLhXA==} + importx@0.2.2: + resolution: {integrity: sha512-JPVUEEo8iNfxG3t8+ZCCk/vaI0RlCrw6sH8cdE8G9sqo7Kolq+ApF2SZXsuWmLZi3Y60GCTwwttIDQP0dbhuwA==} imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -2571,6 +2585,9 @@ packages: resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + regexp-to-ast@0.5.0: + resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} + regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true @@ -3579,6 +3596,11 @@ snapshots: '@pkgr/core@0.1.0': {} + '@prettier/plugin-xml@3.4.1(prettier@3.2.5)': + dependencies: + '@xml-tools/parser': 1.0.11 + prettier: 3.2.5 + '@rollup/rollup-android-arm-eabi@4.4.1': optional: true @@ -3964,6 +3986,10 @@ snapshots: '@vue/shared@3.4.27': {} + '@xml-tools/parser@1.0.11': + dependencies: + chevrotain: 7.1.1 + acorn-jsx@5.3.2(acorn@8.11.3): dependencies: acorn: 8.11.3 @@ -4155,6 +4181,10 @@ snapshots: dependencies: get-func-name: 2.0.2 + chevrotain@7.1.1: + dependencies: + regexp-to-ast: 0.5.0 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -4768,7 +4798,7 @@ snapshots: dependencies: debug: 4.3.4 eslint: 9.2.0 - importx: 0.2.0 + importx: 0.2.2 transitivePeerDependencies: - supports-color @@ -5094,7 +5124,7 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - importx@0.2.0: + importx@0.2.2: dependencies: bundle-require: 4.1.0(esbuild@0.20.2) debug: 4.3.4 @@ -5702,6 +5732,8 @@ snapshots: '@eslint-community/regexpp': 4.10.0 refa: 0.12.1 + regexp-to-ast@0.5.0: {} + regexp-tree@0.1.27: {} regjsparser@0.10.0: diff --git a/src/cli/constants.ts b/src/cli/constants.ts index c7ed4c2fd7..9275d0321f 100644 --- a/src/cli/constants.ts +++ b/src/cli/constants.ts @@ -46,6 +46,7 @@ export const vscodeSettingsString = ` "jsonc", "yaml", "toml", + "xml", "gql", "graphql", "astro" diff --git a/src/configs/formatters.ts b/src/configs/formatters.ts index 1592801e8d..e0f3782934 100644 --- a/src/configs/formatters.ts +++ b/src/configs/formatters.ts @@ -1,5 +1,5 @@ import { isPackageExists } from 'local-pkg' -import { GLOB_ASTRO, GLOB_CSS, GLOB_GRAPHQL, GLOB_HTML, GLOB_LESS, GLOB_MARKDOWN, GLOB_POSTCSS, GLOB_SCSS } from '../globs' +import { GLOB_ASTRO, GLOB_CSS, GLOB_GRAPHQL, GLOB_HTML, GLOB_LESS, GLOB_MARKDOWN, GLOB_POSTCSS, GLOB_SCSS, GLOB_XML } from '../globs' import type { VendoredPrettierOptions } from '../vender/prettier-types' import { ensurePackages, interopDefault, parserPlain } from '../utils' import type { OptionsFormatters, StylisticConfig, TypedFlatConfigItem } from '../types' @@ -17,6 +17,7 @@ export async function formatters( html: true, markdown: true, slidev: isPackageExists('@slidev/cli'), + xml: isPackageExists('@prettier/plugin-xml'), } } @@ -24,6 +25,7 @@ export async function formatters( 'eslint-plugin-format', options.markdown && options.slidev ? 'prettier-plugin-slidev' : undefined, options.astro ? 'prettier-plugin-astro' : undefined, + options.xml ? '@prettier/plugin-xml' : undefined, ]) if (options.slidev && options.markdown !== true && options.markdown !== 'prettier') @@ -50,6 +52,13 @@ export async function formatters( options.prettierOptions || {}, ) + const prettierXmlOptions = { + xmlQuoteAttributes: 'double', + xmlSelfClosingSpace: true, + xmlSortAttributesByKey: false, + xmlWhitespaceSensitivity: 'ignore', + } + const dprintOptions = Object.assign( { indentWidth: typeof indent === 'number' ? indent : 2, @@ -142,6 +151,29 @@ export async function formatters( }) } + if (options.xml) { + configs.push({ + files: [GLOB_XML], + languageOptions: { + parser: parserPlain, + }, + name: 'antfu/formatter/xml', + rules: { + 'format/prettier': [ + 'error', + { + ...prettierXmlOptions, + ...prettierOptions, + parser: 'xml', + plugins: [ + '@prettier/plugin-xml', + ], + }, + ], + }, + }) + } + if (options.markdown) { const formater = options.markdown === true ? 'prettier' diff --git a/src/globs.ts b/src/globs.ts index 44d950adfb..a423c50640 100644 --- a/src/globs.ts +++ b/src/globs.ts @@ -23,6 +23,7 @@ export const GLOB_SVELTE = '**/*.svelte' export const GLOB_VUE = '**/*.vue' export const GLOB_YAML = '**/*.y?(a)ml' export const GLOB_TOML = '**/*.toml' +export const GLOB_XML = '**/*.xml' export const GLOB_HTML = '**/*.htm?(l)' export const GLOB_ASTRO = '**/*.astro' export const GLOB_GRAPHQL = '**/*.{g,graph}ql' @@ -46,6 +47,7 @@ export const GLOB_ALL_SRC = [ GLOB_SVELTE, GLOB_VUE, GLOB_YAML, + GLOB_XML, GLOB_HTML, ] diff --git a/src/types.ts b/src/types.ts index 943e970928..7668a73e82 100644 --- a/src/types.ts +++ b/src/types.ts @@ -65,6 +65,13 @@ export interface OptionsFormatters { */ html?: 'prettier' | boolean + /** + * Enable formatting support for XML. + * + * Currently only support Prettier. + */ + xml?: 'prettier' | boolean + /** * Enable formatting support for Markdown. * diff --git a/src/vender/prettier-types.ts b/src/vender/prettier-types.ts index e1fa234ecb..c9c18cd159 100644 --- a/src/vender/prettier-types.ts +++ b/src/vender/prettier-types.ts @@ -39,7 +39,7 @@ export interface VendoredPrettierOptionsRequired { */ bracketSpacing: boolean /** - * Put the `>` of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line instead of being + * Put the `>` of a multi-line HTML (HTML, XML, JSX, Vue, Angular) element at the end of the last line instead of being * alone on the next line (does not apply to self closing elements). */ bracketSameLine: boolean @@ -93,10 +93,31 @@ export interface VendoredPrettierOptionsRequired { */ vueIndentScriptAndStyle: boolean /** - * Enforce single attribute per line in HTML, Vue and JSX. + * Enforce single attribute per line in HTML, XML, Vue and JSX. * @default false */ singleAttributePerLine: boolean + + /** + * How to handle whitespaces in XML. + * @default "preserve" + */ + xmlQuoteAttributes: 'single' | 'double' | 'preserve' + /** + * Whether to put a space inside the brackets of self-closing XML elements. + * @default true + */ + xmlSelfClosingSpace: boolean + /** + * Whether to sort attributes by key in XML elements. + * @default false + */ + xmlSortAttributesByKey: boolean + /** + * How to handle whitespaces in XML. + * @default "ignore" + */ + xmlWhitespaceSensitivity: 'ignore' | 'strict' | 'preserve' } export type BuiltInParserName = @@ -122,6 +143,7 @@ export type BuiltInParserName = | 'scss' | 'typescript' | 'vue' + | 'xml' | 'yaml' // This utility is here to handle the case where you have an explicit union