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/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",
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 @@
+
+
+
+