From 9caea0af5db6ec8861556a89b3ed8eda59c610cd Mon Sep 17 00:00:00 2001 From: ben12 Date: Sun, 8 Dec 2024 15:50:35 +0100 Subject: [PATCH 1/3] Add Malva plugin support --- README.md | 6 + docs/rules/dprint-malva.md | 29 +++ lib/configs/recommended.ts | 7 + lib/dprint/dprint.ts | 16 +- lib/dprint/malva-config-schema.json | 337 ++++++++++++++++++++++++++++ lib/index.ts | 2 + lib/rules/dprint.ts | 2 + package-lock.json | 113 +++++++++- package.json | 5 + scripts/lib/utils.ts | 3 +- scripts/update-if-needed.ts | 35 +-- test/dprint.json | 6 +- test/rules/dprint-malva.ts | 78 +++++++ 13 files changed, 614 insertions(+), 25 deletions(-) create mode 100644 docs/rules/dprint-malva.md create mode 100644 lib/dprint/malva-config-schema.json create mode 100644 test/rules/dprint-malva.ts diff --git a/README.md b/README.md index e16ed59..f7cc4ad 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ $ npm install -D @dprint/json $ npm install -D @dprint/markdown $ npm install -D @dprint/toml $ npm install -D @dprint/typescript +$ npm install -D dprint-plugin-malva ``` ## 📖 Usage @@ -113,6 +114,7 @@ Then run ESLint with `--fix`! | [@ben_12/dprint/markdown] | Format markdown code with [@dprint/markdown]. | | [@ben_12/dprint/toml] | Format toml code with [@dprint/toml]. | | [@ben_12/dprint/typescript] | Format typescript code with [@dprint/typescript]. | +| [@ben_12/dprint/malva] | Format css/scss/less/sass code with [malva]. | ### Available Configs @@ -124,6 +126,7 @@ Then run ESLint with `--fix`! | [plugin:@ben_12/dprint/markdown-recommended] | Enable the [@ben_12/dprint/markdown] rule. | | [plugin:@ben_12/dprint/toml-recommended] | Enable the [@ben_12/dprint/toml] rule. | | [plugin:@ben_12/dprint/typescript-recommended] | Enable the [@ben_12/dprint/typescript] rule along with the [plugin:@ben_12/dprint/disable-typescript-conflict-rules] preset. | +| [plugin:@ben_12/dprint/malva-recommended] | Enable the [@ben_12/dprint/malva] rule. | - Put the [plugin:@ben_12/dprint/recommended] or [plugin:@ben_12/dprint/disable-conflict-rules] config into the last of your `extends` list in order to ensure disabling conflict rules where came from other base configurations. @@ -149,15 +152,18 @@ Please use GitHub's Issues/PRs. [@dprint/markdown]: https://github.com/dprint/dprint-plugin-markdown [@dprint/toml]: https://github.com/dprint/dprint-plugin-toml [@dprint/typescript]: https://github.com/dprint/dprint-plugin-typescript +[malva]: https://github.com/g-plane/malva [npm]: https://www.npmjs.com/ [@ben_12/dprint/dockerfile]: docs/rules/dprint-dockerfile.md [@ben_12/dprint/json]: docs/rules/dprint-json.md [@ben_12/dprint/markdown]: docs/rules/dprint-markdown.md [@ben_12/dprint/toml]: docs/rules/dprint-toml.md [@ben_12/dprint/typescript]: docs/rules/dprint-typescript.md +[@ben_12/dprint/malva]: docs/rules/dprint-malva.md [plugin:@ben_12/dprint/disable-typescript-conflict-rules]: https://github.com/ben12/eslint-plugin-dprint/blob/master/lib/configs/disable-typescript-conflict-rules.ts [plugin:@ben_12/dprint/dockerfile-recommended]: https://github.com/ben12/eslint-plugin-dprint/blob/master/lib/configs/recommended.ts#L3 [plugin:@ben_12/dprint/json-recommended]: https://github.com/ben12/eslint-plugin-dprint/blob/master/lib/configs/recommended.ts#L10 [plugin:@ben_12/dprint/markdown-recommended]: https://github.com/ben12/eslint-plugin-dprint/blob/master/lib/configs/recommended.ts#L17 [plugin:@ben_12/dprint/toml-recommended]: https://github.com/ben12/eslint-plugin-dprint/blob/master/lib/configs/recommended.ts#L24 [plugin:@ben_12/dprint/typescript-recommended]: https://github.com/ben12/eslint-plugin-dprint/blob/master/lib/configs/recommended.ts#L31 +[plugin:@ben_12/dprint/malva-recommended]: https://github.com/ben12/eslint-plugin-dprint/blob/master/lib/configs/recommended.ts#L39 diff --git a/docs/rules/dprint-malva.md b/docs/rules/dprint-malva.md new file mode 100644 index 0000000..fae8c9d --- /dev/null +++ b/docs/rules/dprint-malva.md @@ -0,0 +1,29 @@ +# @ben_12/dprint/malva + +> Format code with [dprint]. + +## Rule Details + +Run [dprint] to format code. + +## Options + +```jsonc +{ + "@ben_12/dprint/malva": [ + "error", + { + // Use dprint JSON configuration file (default: "dprint.json") + // It may be created using `dprint init` command + // See also https://dprint.dev/config/ + "configFile": "dprint.json", + "config": { + // The TypeScript configuration of dprint + // See also https://dprint.dev/plugins/malva/config/ + } + } + ] +} +``` + +[dprint]: https://github.com/dprint/dprint diff --git a/lib/configs/recommended.ts b/lib/configs/recommended.ts index 17f70b5..343213d 100644 --- a/lib/configs/recommended.ts +++ b/lib/configs/recommended.ts @@ -35,3 +35,10 @@ export const typescriptRecommended = { "@ben_12/dprint/typescript": "warn", }, } + +export const malvaRecommended = { + plugins: ["@ben_12/dprint"], + rules: { + "@ben_12/dprint/malva": "warn", + }, +} diff --git a/lib/dprint/dprint.ts b/lib/dprint/dprint.ts index fb3b5a6..6564dcf 100644 --- a/lib/dprint/dprint.ts +++ b/lib/dprint/dprint.ts @@ -18,18 +18,24 @@ const plugins = [ "@dprint/markdown", "@dprint/toml", "@dprint/dockerfile", + "dprint-plugin-malva", ] as const const formatters: Formatter[] = [] for (const module of plugins) { try { - const plugin = require(module) as Plugin + const packageJson = require(module + "/package.json") let buffer: Buffer | undefined = undefined - if (plugin.getPath) { - buffer = fs.readFileSync(plugin.getPath()) - } else if (plugin.getBuffer) { - buffer = plugin.getBuffer() + if (packageJson.main?.endsWith(".js")) { + const plugin = require(module) as Plugin + if (plugin.getPath) { + buffer = fs.readFileSync(plugin.getPath()) + } else if (plugin.getBuffer) { + buffer = plugin.getBuffer() + } + } else if (packageJson.exports?.["."]?.endsWith(".wasm")) { + buffer = fs.readFileSync(require.resolve(module)) } if (buffer) { const formatter = createFromBuffer(buffer) diff --git a/lib/dprint/malva-config-schema.json b/lib/dprint/malva-config-schema.json new file mode 100644 index 0000000..127e688 --- /dev/null +++ b/lib/dprint/malva-config-schema.json @@ -0,0 +1,337 @@ +{ + "title": "Config", + "description": "Configuration for dprint-plugin-malva.", + "type": "object", + "definitions": { + "preferSingleLine": { + "description": "Control whether items should be placed on single line as possible, even they're originally on multiple lines.", + "type": "boolean" + } + }, + "properties": { + "printWidth": { + "description": "The line width limitation that Malva should *(but not must)* avoid exceeding. Malva will try its best to keep line width less than this value, but it may exceed for some cases, for example, a very very long single word.", + "type": "integer", + "minimum": 0 + }, + "useTabs": { + "description": "Specify use space or tab for indentation.", + "type": "boolean" + }, + "indentWidth": { + "description": "Size of indentation. When enabled `useTabs`, this option may be disregarded, since only one tab will be inserted when indented once.", + "type": "integer", + "minimum": 0 + }, + "lineBreak": { + "description": "Specify use `\\n` (LF) or `\\r\\n` (CRLF) for line break.", + "type": "string", + "enum": [ + "lf", + "crlf" + ] + }, + "hexCase": { + "description": "Control the case of hex color values.", + "type": "string", + "oneOf": [ + { + "description": "Hex color values will be kept as-is.", + "type": "string", + "enum": [ + "ignore" + ] + }, + { + "description": "Hex color values will be converted to lower case.", + "type": "string", + "enum": [ + "lower" + ] + }, + { + "description": "Hex color values will be converted to upper case.", + "type": "string", + "enum": [ + "upper" + ] + } + ] + }, + "hexColorLength": { + "description": "Control the hex color values in short-hand form or long-hand form.", + "type": [ + "string", + "null" + ], + "oneOf": [ + { + "description": "Hex color values will be converted to short-hand form.", + "type": "string", + "enum": [ + "short" + ] + }, + { + "description": "Hex color values will be converted to long-hand form.", + "type": "string", + "enum": [ + "long" + ] + }, + { + "description": "Hex color values will be kept as-is.", + "type": "object", + "enum": [ + null + ] + } + ] + }, + "quotes": { + "description": "Control the quotes of strings.", + "type": "string", + "oneOf": [ + { + "description": "Always use double quotes. Double quotes in strings will be escaped.", + "type": "string", + "enum": [ + "alwaysDouble" + ] + }, + { + "description": "Always use single quotes. Single quotes in strings will be escaped.", + "type": "string", + "enum": [ + "alwaysSingle" + ] + }, + { + "description": "Use double quotes as possible. However if there're double quotes in strings, quotes will be kept as-is.", + "type": "string", + "enum": [ + "preferDouble" + ] + }, + { + "description": "Use single quotes as possible. However if there're single quotes in strings, quotes will be kept as-is.", + "type": "string", + "enum": [ + "preferSingle" + ] + } + ] + }, + "operatorLinebreak": { + "description": "Control whether line break should come before or after operators.", + "type": "string", + "oneOf": [ + { + "description": "Line break will come before operators.", + "type": "string", + "enum": [ + "before" + ] + }, + { + "description": "Line break will come after operators.", + "type": "string", + "enum": [ + "after" + ] + } + ] + }, + "blockSelectorLinebreak": { + "description": "Control line break behavior after selector commas.", + "type": "string", + "oneOf": [ + { + "description": "Always insert line break after comma.", + "type": "string", + "enum": [ + "always" + ] + }, + { + "description": "If the whole selector can be put on a single line, there won't be line breaks; otherwise, there will be line breaks after each comma.", + "type": "string", + "enum": [ + "consistent" + ] + }, + { + "description": "Selector will be put on one line as possible. Once it exceeds `printWidth`, line break will be inserted where the code exceeds `printWidth`.", + "type": "string", + "enum": [ + "wrap" + ] + } + ] + }, + "omitNumberLeadingZero": { + "description": "Control whether omit leading zero before dot of numbers or not.", + "type": "boolean" + }, + "trailingComma": { + "description": "Control whether trailing comma should be inserted or not.", + "type": "boolean" + }, + "formatComments": { + "description": "Control whether whitespace should be inserted at the beginning and end of comments.", + "type": "boolean" + }, + "alignComments": { + "description": "Control whether to tweak multi-line comments indentation.", + "type": "boolean" + }, + "linebreakInPseudoParens": { + "description": "Control whether line break should be inserted in pseudo class/element parens or not if current line is too long.", + "type": "boolean" + }, + "declarationOrder": { + "description": "Control the strategy of sorting CSS declarations (a.k.a. properties). If it's `null`, it won't sort CSS declarations.", + "type": [ + "string", + "null" + ], + "oneOf": [ + { + "description": "Order in a simple alphabetical manner from a - z. This strategy will also sort unknown properties.", + "type": "string", + "enum": [ + "alphabetical" + ] + }, + { + "description": "Order from most important, flow affecting properties, to least important properties. Unknown properties won't be sorted.", + "type": "string", + "enum": [ + "smacss" + ] + }, + { + "description": "Order properties applying outside the box model, moving inward to intrinsic changes. Unknown properties won't be sorted.", + "type": "string", + "enum": [ + "concentric" + ] + }, + { + "description": "Don't sort CSS declarations.", + "type": "string", + "enum": [ + "null" + ] + } + ] + }, + "singleLineBlockThreshold": { + "description": "Control the threshold value for putting block on a single line. If the number of statements in a block is less than or equal to this value, the block will be put on a single line as possible, but when the code can't fit on single line, it will still break into multiple lines.", + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "keyframeSelectorNotation": { + "description": "Control whether to use percentage or keyword (`from` and `to`) notation as keyframe selectors.", + "type": [ + "string", + "null" + ], + "oneOf": [ + { + "description": "Use keyword notation. This only affects `0%` and `100%`. For other percentage values, they will be kept as-is.", + "type": "string", + "enum": [ + "keyword" + ] + }, + { + "description": "Use percentage notation.", + "type": "string", + "enum": [ + "percentage" + ] + }, + { + "description": "Keyframe selector notation will be kept as-is.", + "type": "object", + "enum": [ + null + ] + } + ] + }, + "attrValueQuotes": { + "description": "Control whether should add quotes to attribute value in selector or not if it's not quoted.", + "type": "string", + "oneOf": [ + { + "description": "Always add quotes.", + "type": "string", + "enum": [ + "always" + ] + }, + { + "description": "Don't add quotes to those that're not quoted. For quoted value, quotes won't be removed.", + "type": "string", + "enum": [ + "ignore" + ] + } + ] + }, + "preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "selectors.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "functionArgs.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "sassContentAtRule.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "sassIncludeAtRule.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "sassMap.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "sassModuleConfig.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "sassParams.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "lessImportOptions.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "lessMixinArgs.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "lessMixinParams.preferSingleLine": { + "$ref": "#/definitions/preferSingleLine" + }, + "singleLineTopLevelDeclarations": { + "description": "Control whether to force to format all top-level declarations on a single line.", + "type": "boolean" + }, + "selectorOverrideCommentDirective": { + "description": "Text directive for overriding selector formatting.", + "type": "string" + }, + "ignoreCommentDirective": { + "description": "Text directive for ignoring formatting specific statement.", + "type": "string" + }, + "ignoreFileCommentDirective": { + "description": "Text directive for ignoring formatting a whole file.", + "type": "string" + } + } +} \ No newline at end of file diff --git a/lib/index.ts b/lib/index.ts index cb79358..b806bdf 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -2,6 +2,7 @@ import { disableTypescriptConflictRules } from "./configs/disable-conflict-rules import { dockerfileRecommended, jsonRecommended, + malvaRecommended, markdownRecommended, tomlRecommended, typescriptRecommended, @@ -16,6 +17,7 @@ module.exports = { "markdown-recommended": markdownRecommended, "toml-recommended": tomlRecommended, "typescript-recommended": typescriptRecommended, + "malva-recommended": malvaRecommended, }, rules: dprintRules, } diff --git a/lib/rules/dprint.ts b/lib/rules/dprint.ts index ee3e1b3..d21f030 100644 --- a/lib/rules/dprint.ts +++ b/lib/rules/dprint.ts @@ -4,6 +4,7 @@ import path from "path" import dockerfileConfigSchema from "../dprint/dockerfile-config-schema.json" import { format } from "../dprint/dprint" import jsonConfigSchema from "../dprint/json-config-schema.json" +import malvaConfigSchema from "../dprint/malva-config-schema.json" import markdownConfigSchema from "../dprint/markdown-config-schema.json" import tomlConfigSchema from "../dprint/toml-config-schema.json" import tsConfigSchema from "../dprint/typescript-config-schema.json" @@ -16,6 +17,7 @@ const configSchemas = [ { name: "markdown", configSchema: markdownConfigSchema as JSONSchema4 }, { name: "toml", configSchema: tomlConfigSchema as JSONSchema4 }, { name: "typescript", configSchema: tsConfigSchema as JSONSchema4 }, + { name: "malva", configSchema: malvaConfigSchema as JSONSchema4 }, ] const messages = { diff --git a/package-lock.json b/package-lock.json index 7aebdf6..ad12cce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@types/eslint": "^9.0.0", "@types/node": "^18.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", + "axios": "^1.7.9", "eslint": "^9.0.0", "mocha": "^10.0.0", "nyc": "^15.0.1", @@ -39,6 +40,7 @@ "@dprint/markdown": "^0.17.8", "@dprint/toml": "^0.6.3", "@dprint/typescript": "^0.93.3", + "dprint-plugin-malva": "^0.11.0", "eslint": ">=7.0.0" }, "peerDependenciesMeta": { @@ -55,6 +57,9 @@ "optional": true }, "@dprint/typescript": { + "optional": false + }, + "dprint-plugin-malva": { "optional": true } } @@ -616,8 +621,6 @@ "version": "0.93.3", "resolved": "https://registry.npmjs.org/@dprint/typescript/-/typescript-0.93.3.tgz", "integrity": "sha512-P/AAHYDyUG+5hih8knuk3s9n2wrCD1LSh0YsLlJMx6+v0Wsjf0PpcVRn+xDvHCtwPUctB5WBkZT2U8mu6Cm7RQ==", - "license": "MIT", - "optional": true, "peer": true }, "node_modules/@eslint-community/eslint-utils": { @@ -1512,6 +1515,23 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1733,6 +1753,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1817,6 +1849,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/diff": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", @@ -1825,6 +1866,13 @@ "node": ">=0.3.1" } }, + "node_modules/dprint-plugin-malva": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dprint-plugin-malva/-/dprint-plugin-malva-0.11.0.tgz", + "integrity": "sha512-U1FNaTLiL9jaPajJwLfhFTrYDe9MVV7nI1pT/nIJev3NwZz2Y2DuxamvImhyQOaNZab4mQanqAc7mZ7e8m1+Ig==", + "optional": true, + "peer": true + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2193,6 +2241,26 @@ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -2206,6 +2274,20 @@ "node": ">=8.0.0" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -2924,6 +3006,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3561,6 +3664,12 @@ "node": ">=8" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index e2b7533..914f9e5 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@dprint/markdown": "^0.17.8", "@dprint/toml": "^0.6.3", "@dprint/typescript": "^0.93.3", + "dprint-plugin-malva": "^0.11.0", "eslint": ">=7.0.0" }, "peerDependenciesMeta": { @@ -33,6 +34,9 @@ }, "@dprint/typescript": { "optional": true + }, + "dprint-plugin-malva": { + "optional": true } }, "dependencies": { @@ -49,6 +53,7 @@ "@types/eslint": "^9.0.0", "@types/node": "^18.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", + "axios": "^1.7.9", "eslint": "^9.0.0", "mocha": "^10.0.0", "nyc": "^15.0.1", diff --git a/scripts/lib/utils.ts b/scripts/lib/utils.ts index 2080fc5..0e9c1a0 100644 --- a/scripts/lib/utils.ts +++ b/scripts/lib/utils.ts @@ -10,7 +10,8 @@ export const stdoutOf = (command: string) => { console.log("$", command, "> self") return execSync(command, { encoding: "utf8", - stdio: ["inherit", "pipe", "inherit"], + stdio: ["inherit", "pipe"], + shell: "bash", }) .trim() } diff --git a/scripts/update-if-needed.ts b/scripts/update-if-needed.ts index f0d9f9e..9f8902e 100644 --- a/scripts/update-if-needed.ts +++ b/scripts/update-if-needed.ts @@ -1,5 +1,5 @@ +import axios from "axios" import fs from "fs" -import https from "https" import * as JSONC from "jsonc-parser" import path from "path" import { setGithubOutput, sh, stdoutOf } from "./lib/utils" @@ -16,18 +16,21 @@ enum UpdateKing { interface PluginConfig { name: string - shortName: string schema: (version: string) => { source: string; destination: string } } -function createPluginConfig(language: string): PluginConfig { +function createPluginConfig( + shortName: string, + org: string = "dprint", + plugin: string = `dprint-plugin-${shortName}`, + npmMame: string = `@dprint/${shortName}`, + versionPrefix: string = "", +): PluginConfig { return { - name: `@dprint/${language}`, - shortName: language, + name: npmMame, schema: (version: string) => ({ - source: - `https://raw.githubusercontent.com/dprint/dprint-plugin-${language}/${version}/deployment/schema.json`, - destination: path.join(LibDprintPath, `${language}-config-schema.json`), + source: `https://plugins.dprint.dev/${org}/${plugin}/${versionPrefix}${version}/schema.json`, + destination: path.join(LibDprintPath, `${shortName}-config-schema.json`), }), } } @@ -38,6 +41,7 @@ const plugins: PluginConfig[] = [ createPluginConfig("markdown"), createPluginConfig("toml"), createPluginConfig("typescript"), + createPluginConfig("malva", "g-plane", "malva", "dprint-plugin-malva", "v"), ] type CurrentVersionInfo = { @@ -80,16 +84,15 @@ function readLatestVersion(plugin: PluginConfig): LatestReleaseInfo { * @param srcPath The path to the source code of the config schema. */ async function updateConfigSchema(srcPath: string, destPath: string): Promise { - console.log("Update %o", path.relative(process.cwd(), destPath)) + console.log("Update %o => %o", srcPath, path.relative(process.cwd(), destPath)) const originalContent = await new Promise((resolve, fail) => - https.get(srcPath, response => { - let body = "" - response.setEncoding("utf-8") - const stream = response.on("data", chunk => body += chunk) - stream.on("end", () => resolve(body)) - }) - .on("error", err => fail(err)) + axios({ + url: srcPath, + responseType: "text", + method: "get", + }).then(response => resolve(response.data)) + .catch(err => fail(new Error(err?.message ?? err?.response?.status))) ) const jsonSchema = JSONC.parse(originalContent) diff --git a/test/dprint.json b/test/dprint.json index ac513dc..cdc9192 100644 --- a/test/dprint.json +++ b/test/dprint.json @@ -15,6 +15,9 @@ "toml": { "comment.forceLeadingSpace": false }, + "malva": { + "hexCase": "upper" + }, "excludes": [ "**/node_modules" ], @@ -23,6 +26,7 @@ "node_modules/@dprint/dockerfile/plugin.wasm", "node_modules/@dprint/markdown/plugin.wasm", "node_modules/@dprint/json/plugin.wasm", - "node_modules/@dprint/toml/plugin.wasm" + "node_modules/@dprint/toml/plugin.wasm", + "node_modules/dprint-plugin-malva/plugin.wasm" ] } \ No newline at end of file diff --git a/test/rules/dprint-malva.ts b/test/rules/dprint-malva.ts new file mode 100644 index 0000000..c487dce --- /dev/null +++ b/test/rules/dprint-malva.ts @@ -0,0 +1,78 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-require-imports */ +import { RuleTester } from "eslint" +import { version } from "eslint/package.json" +import path from "path" +import { dprintRules } from "../../lib/rules/dprint" + +const eslintVersion = +version.split(".")[0] + +const tester = eslintVersion >= 9 + ? new RuleTester({ + languageOptions: { + parser: require("@ben_12/eslint-simple-parser"), + }, + } as any) + : new RuleTester({ + parser: require.resolve("@ben_12/eslint-simple-parser"), + } as any) +tester.run("dprint-plugin-malva", dprintRules.json, { + valid: [ + { + filename: path.join(__dirname, "test.css"), + code: ".test {\n color: #ff0000;\n}\n", + options: [{ configFile: "", config: {} }], + }, + { + filename: path.join(__dirname, "test.scss"), + code: ".test {\n color: #f00;\n}\n", + options: [{ configFile: "", config: {} }], + }, + { + filename: path.join(__dirname, "test.less"), + code: ".test {\n color: #ff0000;\n}\n", + options: [{ configFile: "", config: {} }], + }, + { + filename: path.join(__dirname, "test.sass"), + code: ".test\n color: #ff0000\n", + options: [{ configFile: "", config: {} }], + }, // With dprint JSON configuration + { + filename: path.join(__dirname, "test.css"), + code: ".test {\n color: #FF0000;\n}\n", + options: [{ configFile: "test/dprint.json", config: {} }], + }, // Non json file + { + filename: path.join(__dirname, "test.unk"), + code: "Unknown language file", + options: [{ configFile: "", config: {} }], + }, + ], + invalid: [ + { + filename: path.join(__dirname, "test.css"), + code: ".test { color: #f00; }\n", + output: ".test {\n color: #f00;\n}\n", + options: [{ configFile: "", config: {} }], + errors: [ + { + messageId: "requireLinebreak", + data: {}, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + { + messageId: "requireLinebreak", + data: {}, + line: 1, + column: 21, + endLine: 1, + endColumn: 22, + }, + ], + }, + ], +}) From ce4ecc072efee5004337fcd1d4f7e8246cc6c367 Mon Sep 17 00:00:00 2001 From: ben12 Date: Sat, 21 Dec 2024 12:56:43 +0100 Subject: [PATCH 2/3] Find formatter to use with the config name instead of file extension + fix malva unit tests --- lib/dprint/dprint.ts | 82 ++++++++++++++++++++++---------------- lib/rules/dprint.ts | 4 +- test/rules/dprint-malva.ts | 4 +- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/lib/dprint/dprint.ts b/lib/dprint/dprint.ts index 6564dcf..203c20a 100644 --- a/lib/dprint/dprint.ts +++ b/lib/dprint/dprint.ts @@ -12,49 +12,60 @@ interface Plugin { getBuffer?(): Buffer } -const plugins = [ - "@dprint/typescript", - "@dprint/json", - "@dprint/markdown", - "@dprint/toml", - "@dprint/dockerfile", - "dprint-plugin-malva", -] as const - -const formatters: Formatter[] = [] - -for (const module of plugins) { - try { - const packageJson = require(module + "/package.json") - let buffer: Buffer | undefined = undefined - if (packageJson.main?.endsWith(".js")) { - const plugin = require(module) as Plugin - if (plugin.getPath) { - buffer = fs.readFileSync(plugin.getPath()) - } else if (plugin.getBuffer) { - buffer = plugin.getBuffer() +const plugins: Readonly> = { + "typescript": "@dprint/typescript", + "json": "@dprint/json", + "markdown": "@dprint/markdown", + "toml": "@dprint/toml", + "dockerfile": "@dprint/dockerfile", + "malva": "dprint-plugin-malva", +} + +const formatters: Readonly> = Object.entries(plugins).reduce( + (formatters, [name, module]) => { + try { + const packageJson = require(module + "/package.json") + let buffer: Buffer | undefined = undefined + if (packageJson.main?.endsWith(".js")) { + const plugin = require(module) as Plugin + if (plugin.getPath) { + buffer = fs.readFileSync(plugin.getPath()) + } else if (plugin.getBuffer) { + buffer = plugin.getBuffer() + } + } else if (packageJson.exports?.["."]?.endsWith(".wasm")) { + buffer = fs.readFileSync(require.resolve(module)) } - } else if (packageJson.exports?.["."]?.endsWith(".wasm")) { - buffer = fs.readFileSync(require.resolve(module)) - } - if (buffer) { - const formatter = createFromBuffer(buffer) - formatters.push(formatter) + if (buffer) { + const formatter = createFromBuffer(buffer) + formatters[name] = formatter + } + } // eslint-disable-next-line @typescript-eslint/no-unused-vars + catch (e) { + // plugin unavailable } - } // eslint-disable-next-line @typescript-eslint/no-unused-vars - catch (e) { - // plugin unavailable - } -} + return formatters + }, + {} as Record, +) -function getFormatter(filePath: string): Formatter | undefined { - for (const formatter of formatters) { +function getFormatter(filePath: string, configName: string): Formatter | undefined { + const formatter = formatters[configName] + if (formatter) { const pluginInfo = formatter.getPluginInfo() const fileExtensions = pluginInfo.fileExtensions || [] const fileNames = pluginInfo.fileNames || [] const basename = path.basename(filePath) if (fileExtensions.some(ext => basename.endsWith("." + ext)) || fileNames.some(file => file === basename)) { return formatter + } else { + console.warn("File %s not supported by %s", filePath, plugins[configName]) + } + } else { + if (plugins[configName]) { + console.error("Plugin not found: %s", plugins[configName]) + } else { + console.error("Unknown plugin for %s", configName) } } return undefined @@ -76,8 +87,9 @@ export function format( config: Record, filePath: string, fileText: string, + configName: string, ): string | undefined { - const formatter = getFormatter(filePath) + const formatter = getFormatter(filePath, configName) if (formatter) { const configKey = formatter.getPluginInfo().configKey const newConfig = JSON.stringify(config) diff --git a/lib/rules/dprint.ts b/lib/rules/dprint.ts index d21f030..309f255 100644 --- a/lib/rules/dprint.ts +++ b/lib/rules/dprint.ts @@ -229,7 +229,7 @@ export const dprintRules: { [name: string]: Rule.RuleModule } = configSchemas.ma const fileText = sourceCode.getText() const options = context.options[0] ?? defaultOptions const configFile = options.configFile ?? "dprint.json" - const config = options.config || {} + const configOpt = options.config || {} // Needs an absolute path if (!filePath || !path.isAbsolute(filePath)) { @@ -237,7 +237,7 @@ export const dprintRules: { [name: string]: Rule.RuleModule } = configSchemas.ma } // Does format - const formattedText = format(configFile, config, filePath, fileText) + const formattedText = format(configFile, configOpt, filePath, fileText, config.name) if (typeof formattedText !== "string") { return } diff --git a/test/rules/dprint-malva.ts b/test/rules/dprint-malva.ts index c487dce..dd2b147 100644 --- a/test/rules/dprint-malva.ts +++ b/test/rules/dprint-malva.ts @@ -16,7 +16,7 @@ const tester = eslintVersion >= 9 : new RuleTester({ parser: require.resolve("@ben_12/eslint-simple-parser"), } as any) -tester.run("dprint-plugin-malva", dprintRules.json, { +tester.run("dprint-plugin-malva", dprintRules.malva, { valid: [ { filename: path.join(__dirname, "test.css"), @@ -44,7 +44,7 @@ tester.run("dprint-plugin-malva", dprintRules.json, { options: [{ configFile: "test/dprint.json", config: {} }], }, // Non json file { - filename: path.join(__dirname, "test.unk"), + filename: path.join(__dirname, "test.ts"), code: "Unknown language file", options: [{ configFile: "", config: {} }], }, From 49b48e36552f823a4a46dab89c472aa1f684bf45 Mon Sep 17 00:00:00 2001 From: ben12 Date: Sat, 21 Dec 2024 13:12:34 +0100 Subject: [PATCH 3/3] Logs errors on loading dprint plugins --- lib/dprint/dprint.ts | 21 +++++++++++++-------- package-lock.json | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/dprint/dprint.ts b/lib/dprint/dprint.ts index 203c20a..6532645 100644 --- a/lib/dprint/dprint.ts +++ b/lib/dprint/dprint.ts @@ -24,7 +24,14 @@ const plugins: Readonly> = { const formatters: Readonly> = Object.entries(plugins).reduce( (formatters, [name, module]) => { try { - const packageJson = require(module + "/package.json") + let packageJson + try { + packageJson = require(module + "/package.json") + } // eslint-disable-next-line @typescript-eslint/no-unused-vars + catch (e) { + // plugin unavailable + return formatters + } let buffer: Buffer | undefined = undefined if (packageJson.main?.endsWith(".js")) { const plugin = require(module) as Plugin @@ -40,8 +47,8 @@ const formatters: Readonly> = Object.entries(plugins). const formatter = createFromBuffer(buffer) formatters[name] = formatter } - } // eslint-disable-next-line @typescript-eslint/no-unused-vars - catch (e) { + } catch (e) { + console.error("Fail to load plugin", module, ":", e) // plugin unavailable } return formatters @@ -61,12 +68,10 @@ function getFormatter(filePath: string, configName: string): Formatter | undefin } else { console.warn("File %s not supported by %s", filePath, plugins[configName]) } + } else if (plugins[configName]) { + console.error("Plugin not found: %s", plugins[configName]) } else { - if (plugins[configName]) { - console.error("Plugin not found: %s", plugins[configName]) - } else { - console.error("Unknown plugin for %s", configName) - } + console.error("Unknown plugin for %s", configName) } return undefined } diff --git a/package-lock.json b/package-lock.json index ad12cce..3199df3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "optional": true }, "@dprint/typescript": { - "optional": false + "optional": true }, "dprint-plugin-malva": { "optional": true @@ -621,6 +621,7 @@ "version": "0.93.3", "resolved": "https://registry.npmjs.org/@dprint/typescript/-/typescript-0.93.3.tgz", "integrity": "sha512-P/AAHYDyUG+5hih8knuk3s9n2wrCD1LSh0YsLlJMx6+v0Wsjf0PpcVRn+xDvHCtwPUctB5WBkZT2U8mu6Cm7RQ==", + "optional": true, "peer": true }, "node_modules/@eslint-community/eslint-utils": {