Skip to content

Commit c255703

Browse files
authored
Feat: rcs.replace.pug (#61)
(ref: JPeer264/node-rename-css-selectors#22)
1 parent 78840f7 commit c255703

File tree

12 files changed

+522
-2
lines changed

12 files changed

+522
-2
lines changed

docs/api/replace.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ All replace methods take `Buffer` or `String`.
66

77
- [regex](#regex)
88
- [html](#html)
9+
- [pug](#pug)
910
- [any](#any)
1011
- [css](#css)
1112
- [js](#js)
@@ -31,6 +32,7 @@ Options:
3132
- espreeOptions: same as [replace.js](#js) options
3233
- triggerClassAttributes `<Array>`: Array of string or regular expressions. Renames all classes with the matching string or regex. E.g. `[/data-*/ , 'custom-attr']` matches all data attributes and 'custom-attr'
3334
- triggerIdAttributes `<Array>`: Same as triggerClassAttributes just for IDs
35+
3436
Example:
3537

3638
```js
@@ -44,6 +46,35 @@ const replacedHtml = rcs.replace.html('<div class="my-class"></div>');
4446
// '<div class="a"></div>'
4547
```
4648

49+
### pug
50+
51+
> Same as [rcs.replace.html](#html) just with [pug](https://pugjs.org/) templates
52+
53+
**rcs.replace.pug(code[, options])**
54+
55+
Parameters:
56+
- code `<String>`
57+
- same options as [rcs.replace.html](#html) `<Object>`
58+
59+
Example:
60+
61+
```js
62+
const rcs = require('rcs-core');
63+
64+
// first set the id to replace
65+
rcs.selectorLibrary.set('.my-class');
66+
67+
const replacedHtml = rcs.replace.pug(`
68+
.my-class(attr='my attribute')
69+
.another-class
70+
`);
71+
// output in pug:
72+
// `
73+
// .a(attr!='my attribute')
74+
// .another-class
75+
// `
76+
```
77+
4778
### any
4879

4980
> Replaces all strings which matches the filled selectors

lib/replace/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import regex from './regex';
33
import html from './html';
44
import any from './any';
55
import css from './css';
6+
import pug from './pug';
67
import js from './js';
78

89
export default {
@@ -11,5 +12,6 @@ export default {
1112
html,
1213
any,
1314
css,
15+
pug,
1416
js,
1517
};

lib/replace/pug.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import lex from 'pug-lexer';
2+
import walk from 'pug-walk';
3+
import parser from 'pug-parser';
4+
import wrap from 'pug-runtime/wrap';
5+
import generateCode from 'pug-code-gen';
6+
import generateSource from 'pug-source-gen';
7+
import merge from 'lodash.merge';
8+
9+
import replaceCss from './css';
10+
import replaceJs from './js';
11+
import selectorLibrary from '../selectorLibrary';
12+
13+
const shouldTriggerAttribute = (attr, item) => (
14+
item instanceof RegExp
15+
? attr.name.match(new RegExp(item))
16+
: item === attr.name
17+
);
18+
19+
const replacePug = (code, opts = {}) => {
20+
const lexed = lex(code);
21+
const ast = parser(lexed);
22+
const defaultOptions = {
23+
espreeOptions: {},
24+
triggerClassAttributes: [],
25+
triggerIdAttributes: [],
26+
};
27+
28+
const options = merge(opts, defaultOptions);
29+
30+
walk(ast, (node) => {
31+
if (node.name === 'script' || node.name === 'style') {
32+
const newCode = wrap(generateCode(node.block))();
33+
const replacedCode = node.name === 'script'
34+
? replaceJs(newCode, options.espreeOptions)
35+
: replaceCss(newCode);
36+
37+
// add one tab after each new line
38+
const pugCode = `${node.name}.\n${replacedCode}`.replace(/\n/g, '\n\t');
39+
const astReplaced = parser(lex(pugCode));
40+
const scriptBlock = astReplaced.nodes[0].block;
41+
42+
// do not change entire scriptBlock
43+
// this might be look like the correct ast,
44+
// but the begin and end loc numbers are wrong
45+
// eslint-disable-next-line no-param-reassign
46+
node.block.nodes = node.block.nodes.map((n, i) => (
47+
Object.assign({}, n, {
48+
val: scriptBlock.nodes[i].val,
49+
})
50+
));
51+
}
52+
53+
if (Array.isArray(node.attrs) && node.attrs.length >= 0) {
54+
node.attrs.forEach((attr) => {
55+
let selectorType;
56+
let shouldReplace = false;
57+
58+
if (
59+
attr.name === 'class' ||
60+
options.triggerClassAttributes.some((...params) => (
61+
shouldTriggerAttribute(attr, ...params)
62+
))
63+
) {
64+
selectorType = '.';
65+
shouldReplace = true;
66+
}
67+
68+
if (
69+
attr.name === 'id' ||
70+
options.triggerIdAttributes.some((...params) => (
71+
shouldTriggerAttribute(attr, ...params)
72+
))
73+
) {
74+
selectorType = '#';
75+
shouldReplace = true;
76+
}
77+
78+
if (!shouldReplace) {
79+
return;
80+
}
81+
82+
// attr.val includes ' in the beginning and the end
83+
// remove them and reattach them after
84+
const prefix = attr.val.charAt(0);
85+
const suffix = attr.val.charAt(attr.val.length - 1);
86+
const val = attr.val.slice(1, (attr.val.length - 1));
87+
88+
// following will replace each whitespace
89+
// seperated value with its renamed one
90+
const replacedVal = val
91+
.split(' ')
92+
.map(value => (
93+
// renaming each value
94+
selectorLibrary
95+
.get(`${selectorType}${value}`)
96+
.replace(new RegExp(`^\\${selectorType}`), '')
97+
))
98+
.join(' ');
99+
100+
// eslint-disable-next-line no-param-reassign
101+
attr.val = prefix + replacedVal + suffix;
102+
});
103+
}
104+
});
105+
106+
return generateSource(ast);
107+
};
108+
109+
export default replacePug;

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@
5252
"parse5": "^5.0.0",
5353
"parse5-traverse": "^1.0.3",
5454
"postcss": "^6.0.17",
55+
"pug-code-gen": "^2.0.1",
56+
"pug-lexer": "^4.0.0",
57+
"pug-parser": "^5.0.0",
58+
"pug-runtime": "^2.0.4",
59+
"pug-source-gen": "^0.0.2",
60+
"pug-walk": "^1.1.7",
5561
"recast": "^0.15.0"
5662
},
5763
"devDependencies": {

test/files/fixtures/pug/index.pug

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
doctype html
2+
head
3+
meta(charset='UTF-8')
4+
title Test Document
5+
.jp-block
6+
.jp-block__element

test/files/fixtures/pug/script.pug

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
doctype html
2+
head
3+
meta(charset='UTF-8')
4+
meta(name='viewport' content='width=device-width, initial-scale=1.0')
5+
meta(http-equiv='X-UA-Compatible' content='ie=edge')
6+
title Document
7+
table#id.test.selector
8+
div
9+
div
10+
.some-class
11+
table#id.test.selector
12+
script.
13+
const myClass = "selector";
14+
const anotherClass = "test";
15+
const id = "id";

test/files/fixtures/pug/style.pug

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
doctype html
2+
head
3+
meta(charset='UTF-8')
4+
title Test Document
5+
style.
6+
.jp-block {
7+
content: 'block';
8+
}
9+
.jp-block__element {
10+
content: 'block__element';
11+
}
12+
.jp-block
13+
.jp-block__element

test/files/results/pug/index.pug

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
doctype html
2+
head
3+
meta(charset!='UTF-8')
4+
title Test Document
5+
.a
6+
.b

test/files/results/pug/script.pug

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
doctype html
2+
head
3+
meta(charset!='UTF-8')
4+
meta(name!='viewport' content!='width=device-width, initial-scale=1.0')
5+
meta(http-equiv!='X-UA-Compatible' content!='ie=edge')
6+
title Document
7+
table#c.a.b
8+
div
9+
div
10+
.some-class
11+
table#c.a.b
12+
script.
13+
const myClass = "b";
14+
const anotherClass = "a";
15+
const id = "c";

test/files/results/pug/style.pug

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
doctype html
2+
head
3+
meta(charset!='UTF-8')
4+
title Test Document
5+
style.
6+
.a {
7+
content: 'block';
8+
}
9+
.b {
10+
content: 'block__element';
11+
}
12+
.a
13+
.b

0 commit comments

Comments
 (0)