├── .editorconfig
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── index.js
├── package.json
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | tmp
4 | .idea
5 | .vscode
6 | typings
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - 'node'
5 | - '6'
6 | - '4'
7 | - '0.12'
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Denis Malinochkin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # posthtml-attrs-sorter
2 |
3 | > A [PostHTML](https://github.com/posthtml/posthtml) plugin for sort attribute of the tag based on the given order.
4 |
5 | [](https://travis-ci.org/mrmlnc/posthtml-attrs-sorter)
6 | [](https://www.npmjs.com/package/posthtml-attrs-sorter)
7 | [](https://david-dm.org/mrmlnc/posthtml-attrs-sorter#info=dependencies)
8 | [](https://david-dm.org/mrmlnc/posthtml-attrs-sorter#info=devDependencies)
9 |
10 | ## Install
11 |
12 | ```shell
13 | $ npm i -D posthtml-attrs-sorter
14 | ```
15 |
16 | ## Why?
17 |
18 | To follow the guidelines for writing code such as [Code Guide by @mdo](http://codeguide.co/).
19 |
20 | ## Usage
21 |
22 | ```js
23 | const posthtml = require('posthtml');
24 | const attrsSorter = require('posthtml-attrs-sorter');
25 | const htmlRaw = fs.readFileSync('path/to/file.html');
26 |
27 | posthtml()
28 | .use(attrsSorter({
29 | // Options
30 | }))
31 | .process(htmlRaw)
32 | .then(function(result) {
33 | fs.writeFileSync('path/to/file.html');
34 | })
35 | ```
36 |
37 | You can also use this plugin in the PostHTML plugin to [Gulp](https://www.npmjs.com/package/gulp-posthtml) or [Grunt](https://www.npmjs.com/package/grunt-posthtml).
38 |
39 | ## Options
40 |
41 | #### order
42 |
43 | * Type: `string[]`
44 | * Info:
45 | * Strings are turned into [Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
46 | * Use `$unknown$` to specify where the unsorted attributes are placed
47 | * Default: http://codeguide.co/#html-attribute-order
48 |
49 | ```json
50 | {
51 | "order": [
52 | "class", "id", "name",
53 | "data-.+", "ng-.+", "src",
54 | "for", "type", "href",
55 | "values", "title", "alt",
56 | "role", "aria-.+",
57 | "$unknown$"
58 | ]
59 | }
60 | ```
61 |
62 | An array of attributes in the correct order.
63 |
64 | ## Plugins for editors and IDE
65 |
66 | * [Visual Studio Code](https://github.com/mrmlnc/vscode-attrs-sorter)
67 |
68 | ## Changelog
69 |
70 | See the [Releases section of our GitHub project](https://github.com/mrmlnc/posthtml-attrs-sorter/releases) for changelogs for each release version.
71 |
72 | ## License
73 |
74 | MIT.
75 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = function(opts) {
2 | // Added Angular after data. See https://github.com/mdo/code-guide/issues/106
3 | var orderList = (opts && opts.order) || [
4 | 'class', 'id', 'name',
5 | 'data-.+', 'ng-.+', 'src',
6 | 'for', 'type', 'href',
7 | 'values', 'title', 'alt',
8 | 'role', 'aria-.+',
9 | '$unknown$'
10 | ];
11 |
12 | // A RegExp's for filtering and sorting
13 | var orderListFilterRegExp = new RegExp('^(' + orderList.join('|') + ')$');
14 | var orderListRegExp = orderList.map(function(item) {
15 | return new RegExp('^' + item + '$');
16 | });
17 |
18 | return function(tree) {
19 | tree.walk(function(node) {
20 | if (!node.attrs) {
21 | return node;
22 | }
23 |
24 | var attrs = Object.keys(node.attrs);
25 |
26 | if (attrs.length === 1 || orderList.length === 0) {
27 | return node;
28 | }
29 |
30 | var sortableAttrs = [];
31 | var sortedAttrs = [];
32 | var notSortedAttrs = [];
33 | var notSortedAttrsIndex = orderList.indexOf('$unknown$');
34 | var finalAttrs = {};
35 |
36 | if (notSortedAttrsIndex === -1) {
37 | notSortedAttrsIndex = orderList.length;
38 | }
39 |
40 | sortableAttrs = attrs
41 | // The separation of the attributes on a sortable and not sortable basis
42 | .filter(function(attr) {
43 | if (orderListFilterRegExp.test(attr)) {
44 | return true;
45 | }
46 |
47 | notSortedAttrs.push(attr);
48 |
49 | return false;
50 | });
51 |
52 | sortedAttrs = orderListRegExp
53 | // match to each position
54 | .map(function(regex) {
55 | return sortableAttrs
56 | // attrs that belong in this regex group
57 | .filter(function(attr) {
58 | return regex.test(attr);
59 | })
60 | // alpha desc sort each group
61 | .sort(function(a, b) {
62 | return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
63 | });
64 | })
65 | // remove empty groups
66 | .filter(function(group) {
67 | return group.length > 0;
68 | });
69 |
70 | sortedAttrs
71 | // put the non-sorted attributes in desired slot
72 | .splice(notSortedAttrsIndex, 0, notSortedAttrs);
73 |
74 | sortedAttrs.forEach(function(group) {
75 | group.forEach(function(attr) {
76 | finalAttrs[attr] = (node.attrs[attr]) ? node.attrs[attr] : true;
77 | });
78 | });
79 |
80 | node.attrs = finalAttrs;
81 |
82 | return node;
83 | });
84 |
85 | return tree;
86 | };
87 | };
88 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "posthtml-attrs-sorter",
3 | "description": "The sorting attribute of the tag based on the given order",
4 | "version": "1.1.0",
5 | "author": {
6 | "name": "Denis Malinochkin",
7 | "url": "canonium.com"
8 | },
9 | "repository": "mrmlnc/posthtml-attrs-sorter",
10 | "license": "MIT",
11 | "keywords": [
12 | "posthtml",
13 | "html",
14 | "posthtml-plugin",
15 | "attrs",
16 | "attributes",
17 | "sorter",
18 | "sort"
19 | ],
20 | "devDependencies": {
21 | "mocha": "^3.0.1",
22 | "posthtml": "^0.9.0",
23 | "xo": "^0.16.0"
24 | },
25 | "engines": {
26 | "node": ">= 0.12.0"
27 | },
28 | "scripts": {
29 | "test": "xo && mocha"
30 | },
31 | "files": [
32 | "index.js"
33 | ],
34 | "xo": {
35 | "rules": {
36 | "indent": [2, 2, { "SwitchCase": 1 }],
37 | "arrow-parens": [2, "always"],
38 | "object-curly-spacing": [2, "always"],
39 | "babel/object-curly-spacing": 0,
40 | "space-before-function-paren": [2, "never"],
41 | "generator-star-spacing": [2, "after"]
42 | },
43 | "envs": [
44 | "node",
45 | "mocha"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var posthtml = require('posthtml');
3 | var m = require('./');
4 |
5 | var test = function(input, output, opts, done) {
6 | posthtml()
7 | .use(m(opts))
8 | .process(input)
9 | .then(function(result) {
10 | assert.equal(result.html, output);
11 | done();
12 | })
13 | .catch(function(err) {
14 | done(err);
15 | });
16 | };
17 |
18 | it('Standard test', function(done) {
19 | test(
20 | '',
21 | '',
22 | {},
23 | done
24 | );
25 | });
26 |
27 | it('Angular test', function(done) {
28 | test(
29 | '
',
30 | '
',
31 | {},
32 | done
33 | );
34 | });
35 |
36 | it('Aria test', function(done) {
37 | test(
38 | 'A checkbox label',
39 | 'A checkbox label',
40 | {},
41 | done
42 | );
43 | });
44 |
45 | it('Custom config test', function(done) {
46 | test(
47 | '',
48 | '',
49 | {
50 | order: ['href', 'id', 'class']
51 | },
52 | done
53 | );
54 | });
55 |
56 | it('Empty config test', function(done) {
57 | test(
58 | '',
59 | '',
60 | {
61 | order: []
62 | },
63 | done
64 | );
65 | });
66 |
67 | it('Custom config bug (issue #12)', function(done) {
68 | test(
69 | '
',
70 | '
',
71 | {
72 | order: [
73 | 'id',
74 | 'class',
75 | 'src',
76 | 'width',
77 | 'height',
78 | 'for',
79 | 'type',
80 | 'href',
81 | 'title',
82 | 'alt',
83 | 'value'
84 | ]
85 | },
86 | done
87 | );
88 | });
89 |
90 | it('Put unsorted in specific location', function(done) {
91 | test(
92 | '
',
93 | '
',
94 | {
95 | order: [
96 | 'src',
97 | 'id',
98 | '$unknown$',
99 | 'class'
100 | ]
101 | },
102 | done
103 | );
104 | });
105 |
--------------------------------------------------------------------------------