-
Notifications
You must be signed in to change notification settings - Fork 7
/
plugin.js
123 lines (102 loc) · 2.93 KB
/
plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import postcss from 'postcss'
module.exports = postcss.plugin(
'postcss-selector-namespace',
(options = {}) => {
let {
namespace = '.self',
selfSelector = /:--namespace/,
rootSelector = /:root/,
ignoreRoot = true,
dropRoot = true,
} = options
selfSelector = regexpToGlobalRegexp(selfSelector)
return (css, result) => {
const computedNamespace =
typeof namespace === 'string'
? namespace
: namespace(css.source.input.file)
if (!computedNamespace) {
return
}
css.walkRules(rule => {
if (canNamespaceSelectors(rule)) {
return
}
rule.selectors = rule.selectors.map(selector =>
namespaceSelector(selector, computedNamespace),
)
})
}
function namespaceSelector(selector, computedNamespace) {
if (hasSelfSelector(selector)) {
return selector.replace(selfSelector, computedNamespace)
}
if (hasRootSelector(selector)) {
return dropRootSelector(selector)
}
return `${computedNamespace} ${selector}`
}
function hasSelfSelector(selector) {
selfSelector.lastIndex = 0
return selfSelector.test(selector)
}
function hasRootSelector(selector) {
return ignoreRoot && selector.search(rootSelector) === 0
}
function dropRootSelector(selector) {
if (dropRoot) {
return selector.replace(rootSelector, '').trim() || selector
}
return selector
}
},
)
/**
* Returns true if the rule selectors can be namespaces
*
* @param {postcss.Rule} rule The rule to check
* @return {boolean} whether the rule selectors can be namespaced or not
*/
function canNamespaceSelectors(rule) {
return hasParentRule(rule) || parentIsAllowedAtRule(rule)
}
/**
* Returns true if the parent rule is a not a media or supports atrule
*
* @param {postcss.Rule} rule The rule to check
* @return {boolean} true if the direct parent is a keyframe rule
*/
function parentIsAllowedAtRule(rule) {
return (
rule.parent &&
rule.parent.type === 'atrule' &&
!/(?:media|supports|for)$/.test(rule.parent.name)
)
}
/**
* Returns true if any parent rule is of type 'rule'
*
* @param {postcss.Rule|postcss.Root|postcss.AtRule} rule The rule to check
* @return {boolean} true if any parent rule is of type 'rule' else false
*/
function hasParentRule(rule) {
if (!rule.parent) {
return false
}
if (rule.parent.type === 'rule') {
return true
}
return hasParentRule(rule.parent)
}
/**
* Newer javascript engines allow setting flags when passing existing regexp
* to the RegExp constructor, until then, we extract the regexp source and
* build a new object.
*
* @param {RegExp|string} regexp The regexp to modify
* @return {RegExp} The new regexp instance
*/
function regexpToGlobalRegexp(regexp) {
let source = regexp instanceof RegExp ? regexp.source : regexp
return new RegExp(source, 'g')
}