From 7bad52034f506551378d4f05d247b948d461c153 Mon Sep 17 00:00:00 2001 From: Andrew Kazakov Date: Thu, 6 Jun 2024 20:38:35 +0300 Subject: [PATCH 1/2] Add a means to ignore certain attributes when sorting --- README.md | 30 +++++++++++++++++++ src/index.ts | 30 ++++++++++++++++--- src/organize.ts | 30 +++++++++++++++++-- .../html/ignore-attribute-basic/config.json | 1 + .../html/ignore-attribute-basic/expected.html | 2 ++ .../html/ignore-attribute-basic/input.html | 10 +++++++ .../config.json | 1 + .../expected.html | 3 ++ .../input.html | 11 +++++++ .../config.json | 1 + .../expected.html | 3 ++ .../input.html | 15 ++++++++++ .../ignore-attribute-multiple/config.json | 1 + .../ignore-attribute-multiple/expected.html | 3 ++ .../html/ignore-attribute-multiple/input.html | 11 +++++++ .../ignore-attribute-text-between/config.json | 1 + .../expected.html | 3 ++ .../ignore-attribute-text-between/input.html | 11 +++++++ .../vue/ignore-attribute-basic/config.json | 1 + .../vue/ignore-attribute-basic/expected.vue | 4 +++ .../vue/ignore-attribute-basic/input.vue | 12 ++++++++ 21 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 src/tests/html/ignore-attribute-basic/config.json create mode 100644 src/tests/html/ignore-attribute-basic/expected.html create mode 100644 src/tests/html/ignore-attribute-basic/input.html create mode 100644 src/tests/html/ignore-attribute-comment-between/config.json create mode 100644 src/tests/html/ignore-attribute-comment-between/expected.html create mode 100644 src/tests/html/ignore-attribute-comment-between/input.html create mode 100644 src/tests/html/ignore-attribute-empty-lines-between/config.json create mode 100644 src/tests/html/ignore-attribute-empty-lines-between/expected.html create mode 100644 src/tests/html/ignore-attribute-empty-lines-between/input.html create mode 100644 src/tests/html/ignore-attribute-multiple/config.json create mode 100644 src/tests/html/ignore-attribute-multiple/expected.html create mode 100644 src/tests/html/ignore-attribute-multiple/input.html create mode 100644 src/tests/html/ignore-attribute-text-between/config.json create mode 100644 src/tests/html/ignore-attribute-text-between/expected.html create mode 100644 src/tests/html/ignore-attribute-text-between/input.html create mode 100644 src/tests/vue/ignore-attribute-basic/config.json create mode 100644 src/tests/vue/ignore-attribute-basic/expected.vue create mode 100644 src/tests/vue/ignore-attribute-basic/input.vue diff --git a/README.md b/README.md index eef9944..ebd2870 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,36 @@ Read below for writing custom attribute orders and configurations ⤵️
``` +--- + +### Keep certain attributes in place + +You can prevent certain attributes from being moved altogether with `prettier-ignore-organize-attributes` comment: + +```json5 +// .prettierrc +{ + "plugins": ["prettier-plugin-organize-attributes"], + "attributeGroups": ["^a$", "^b$", "^c$", "^d$"] +} +``` + +Input: + +```html + +
+``` + +*(Note that attributes should be space separated)* + +Output: + +```html + +
+``` + ## Presets ### HTML diff --git a/src/index.ts b/src/index.ts index aecb6fb..24b7e8d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,6 +50,14 @@ function wrapParser(parser: Parser): Parser { }; } +function getPrettierIgnoreAttributeCommentData(value?: string) { + const match = value + ?.trim() + .match(/^prettier-ignore-organize-attributes\s+(.+)$/s); + + return match?.[1].split(/\s+/) || []; +} + function transformPostParse(parse: Parser["parse"]): Parser["parse"] { return (text, options) => transformRootNode( @@ -89,7 +97,8 @@ function transformNode( node: HTMLNode, groups: string[], sort: OrganizeOptionsSort, - ignoreCase = true + ignoreCase = true, + ignoredAttributes?: string[], ): void { if (node.attrs) { node.attrs = miniorganize(node.attrs, { @@ -98,12 +107,25 @@ function transformNode( groups, sort, map: ({ name }) => name, + ignoredAttributes, }).flat; } - node.children?.forEach((child) => - transformNode(child, groups, sort, ignoreCase) - ); + let foundIgnoredAttributes: string[] | undefined; + node.children?.forEach((child) => { + transformNode(child, groups, sort, ignoreCase, foundIgnoredAttributes); + + if (child.type === "comment") { + const currentIgnoredAttributes = getPrettierIgnoreAttributeCommentData( + child.value, + ); + if (currentIgnoredAttributes.length > 0) { + foundIgnoredAttributes = currentIgnoredAttributes; + } + } else if (!(child.type === "text" && (child.value || "").trim() === "")) { + foundIgnoredAttributes = undefined; + } + }); } export type PrettierPluginOrganizeAttributesParserOptions = { diff --git a/src/organize.ts b/src/organize.ts index 2e34143..a05b659 100644 --- a/src/organize.ts +++ b/src/organize.ts @@ -19,6 +19,7 @@ export interface BaseOrganizeOptions { groups: GroupKey[]; sort?: OrganizeOptionsSort; ignoreCase?: boolean; + ignoredAttributes?: string[]; } export type OrganizeOptionsSort = "ASC" | "DESC" | boolean; @@ -90,9 +91,16 @@ export function miniorganize( } }; - values.forEach((value) => { + const ignoredValuesInfo: { value: TValue; position: number }[] = []; + + values.forEach((value, position) => { const mapped = getString(value); + if (options.ignoredAttributes?.includes(mapped)) { + ignoredValuesInfo.push({ value, position }); + return; + } + for (let group of groups) { if (group.regexp && mapped.match(group.regexp)) { group.values.push(value); @@ -113,8 +121,26 @@ export function miniorganize( }); } + const nonIgnoredValues = groups.flatMap((group) => group.values); + const allValues: TValue[] = []; + for ( + let i = 0, nonIgnoredValuesChecked = 0; + i < ignoredValuesInfo.length; + i++ + ) { + const { value, position } = ignoredValuesInfo[i]; + const nonIgnoredValuesToRemoveCount = + position - nonIgnoredValuesChecked - i; + allValues.push( + ...nonIgnoredValues.splice(0, nonIgnoredValuesToRemoveCount), + value, + ); + nonIgnoredValuesChecked += nonIgnoredValuesToRemoveCount; + } + allValues.push(...nonIgnoredValues); + return { - flat: groups.flatMap((group) => group.values), + flat: allValues, groups: groups.map(({ query, values }) => ({ query, values })), }; } diff --git a/src/tests/html/ignore-attribute-basic/config.json b/src/tests/html/ignore-attribute-basic/config.json new file mode 100644 index 0000000..6689077 --- /dev/null +++ b/src/tests/html/ignore-attribute-basic/config.json @@ -0,0 +1 @@ +{"attributeGroups": ["^a$", "^b$", "^c$", "^d$"]} diff --git a/src/tests/html/ignore-attribute-basic/expected.html b/src/tests/html/ignore-attribute-basic/expected.html new file mode 100644 index 0000000..22bee55 --- /dev/null +++ b/src/tests/html/ignore-attribute-basic/expected.html @@ -0,0 +1,2 @@ + +
diff --git a/src/tests/html/ignore-attribute-basic/input.html b/src/tests/html/ignore-attribute-basic/input.html new file mode 100644 index 0000000..1142292 --- /dev/null +++ b/src/tests/html/ignore-attribute-basic/input.html @@ -0,0 +1,10 @@ + +
diff --git a/src/tests/html/ignore-attribute-comment-between/config.json b/src/tests/html/ignore-attribute-comment-between/config.json new file mode 100644 index 0000000..6689077 --- /dev/null +++ b/src/tests/html/ignore-attribute-comment-between/config.json @@ -0,0 +1 @@ +{"attributeGroups": ["^a$", "^b$", "^c$", "^d$"]} diff --git a/src/tests/html/ignore-attribute-comment-between/expected.html b/src/tests/html/ignore-attribute-comment-between/expected.html new file mode 100644 index 0000000..e095a2a --- /dev/null +++ b/src/tests/html/ignore-attribute-comment-between/expected.html @@ -0,0 +1,3 @@ + + +
diff --git a/src/tests/html/ignore-attribute-comment-between/input.html b/src/tests/html/ignore-attribute-comment-between/input.html new file mode 100644 index 0000000..1da2e6a --- /dev/null +++ b/src/tests/html/ignore-attribute-comment-between/input.html @@ -0,0 +1,11 @@ + + +
diff --git a/src/tests/html/ignore-attribute-empty-lines-between/config.json b/src/tests/html/ignore-attribute-empty-lines-between/config.json new file mode 100644 index 0000000..6689077 --- /dev/null +++ b/src/tests/html/ignore-attribute-empty-lines-between/config.json @@ -0,0 +1 @@ +{"attributeGroups": ["^a$", "^b$", "^c$", "^d$"]} diff --git a/src/tests/html/ignore-attribute-empty-lines-between/expected.html b/src/tests/html/ignore-attribute-empty-lines-between/expected.html new file mode 100644 index 0000000..1a700b2 --- /dev/null +++ b/src/tests/html/ignore-attribute-empty-lines-between/expected.html @@ -0,0 +1,3 @@ + + +
diff --git a/src/tests/html/ignore-attribute-empty-lines-between/input.html b/src/tests/html/ignore-attribute-empty-lines-between/input.html new file mode 100644 index 0000000..63f8346 --- /dev/null +++ b/src/tests/html/ignore-attribute-empty-lines-between/input.html @@ -0,0 +1,15 @@ + + + + + + +
diff --git a/src/tests/html/ignore-attribute-multiple/config.json b/src/tests/html/ignore-attribute-multiple/config.json new file mode 100644 index 0000000..6689077 --- /dev/null +++ b/src/tests/html/ignore-attribute-multiple/config.json @@ -0,0 +1 @@ +{"attributeGroups": ["^a$", "^b$", "^c$", "^d$"]} diff --git a/src/tests/html/ignore-attribute-multiple/expected.html b/src/tests/html/ignore-attribute-multiple/expected.html new file mode 100644 index 0000000..04658cc --- /dev/null +++ b/src/tests/html/ignore-attribute-multiple/expected.html @@ -0,0 +1,3 @@ + + +
diff --git a/src/tests/html/ignore-attribute-multiple/input.html b/src/tests/html/ignore-attribute-multiple/input.html new file mode 100644 index 0000000..9ebed05 --- /dev/null +++ b/src/tests/html/ignore-attribute-multiple/input.html @@ -0,0 +1,11 @@ + + +
diff --git a/src/tests/html/ignore-attribute-text-between/config.json b/src/tests/html/ignore-attribute-text-between/config.json new file mode 100644 index 0000000..6689077 --- /dev/null +++ b/src/tests/html/ignore-attribute-text-between/config.json @@ -0,0 +1 @@ +{"attributeGroups": ["^a$", "^b$", "^c$", "^d$"]} diff --git a/src/tests/html/ignore-attribute-text-between/expected.html b/src/tests/html/ignore-attribute-text-between/expected.html new file mode 100644 index 0000000..678e45b --- /dev/null +++ b/src/tests/html/ignore-attribute-text-between/expected.html @@ -0,0 +1,3 @@ + +This text will negate the ignore directive +
diff --git a/src/tests/html/ignore-attribute-text-between/input.html b/src/tests/html/ignore-attribute-text-between/input.html new file mode 100644 index 0000000..8b6d54f --- /dev/null +++ b/src/tests/html/ignore-attribute-text-between/input.html @@ -0,0 +1,11 @@ + +This text will negate the ignore directive +
diff --git a/src/tests/vue/ignore-attribute-basic/config.json b/src/tests/vue/ignore-attribute-basic/config.json new file mode 100644 index 0000000..6689077 --- /dev/null +++ b/src/tests/vue/ignore-attribute-basic/config.json @@ -0,0 +1 @@ +{"attributeGroups": ["^a$", "^b$", "^c$", "^d$"]} diff --git a/src/tests/vue/ignore-attribute-basic/expected.vue b/src/tests/vue/ignore-attribute-basic/expected.vue new file mode 100644 index 0000000..8def0db --- /dev/null +++ b/src/tests/vue/ignore-attribute-basic/expected.vue @@ -0,0 +1,4 @@ + diff --git a/src/tests/vue/ignore-attribute-basic/input.vue b/src/tests/vue/ignore-attribute-basic/input.vue new file mode 100644 index 0000000..44c8f81 --- /dev/null +++ b/src/tests/vue/ignore-attribute-basic/input.vue @@ -0,0 +1,12 @@ + From d9d35a88bc33b9e6126626ec5b3451028b61a6ce Mon Sep 17 00:00:00 2001 From: Andrew Kazakov Date: Thu, 6 Jun 2024 20:42:03 +0300 Subject: [PATCH 2/2] Use cross-env in commands to run tests --- package-lock.json | 34 +++++++++++++++++++++++++++++++--- package.json | 6 +++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 28f6424..717b6d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "prettier-plugin-organize-attributes", - "version": "0.0.5", + "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prettier-plugin-organize-attributes", - "version": "0.0.5", + "version": "1.0.0", "license": "MIT", "devDependencies": { "@types/jest": "29.1.1", "@types/node": "^14.14.2", "codecov": "^3.8.0", + "cross-env": "^7.0.3", "jest": "29.1.1", "prettier": "^3.0.0", "ts-jest": "29.1.1", @@ -19,7 +20,7 @@ "typescript": "^5.0.0" }, "engines": { - "node": ">=11.0.0" + "node": ">=14.0.0" }, "peerDependencies": { "prettier": "^3.0.0" @@ -1541,6 +1542,24 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5033,6 +5052,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/package.json b/package.json index d4f6c51..6d2b282 100644 --- a/package.json +++ b/package.json @@ -27,21 +27,21 @@ "main": "lib/index", "types": "lib/index", "scripts": { - "test": "NODE_OPTIONS=--experimental-vm-modules jest", + "test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest", + "test:watch": "npm run test -- --watch", "build": "tsc --pretty", "watch": "npm run build -- --watch", - "watch:test": "NODE_OPTIONS=--experimental-vm-modules jest --watch", "release:plugin": "npm run test && npm run build && npm publish", "release:plugin:local": "npm run test && npm run build && npm publish --registry=http://localhost:4873/" }, "peerDependencies": { "prettier": "^3.0.0" }, - "dependencies": {}, "devDependencies": { "@types/jest": "29.1.1", "@types/node": "^14.14.2", "codecov": "^3.8.0", + "cross-env": "^7.0.3", "jest": "29.1.1", "prettier": "^3.0.0", "ts-jest": "29.1.1",