Skip to content

Commit 0f9eaca

Browse files
committed
Add a new option to ignore rules already prefixed
1 parent 52dd421 commit 0f9eaca

File tree

13 files changed

+969
-22
lines changed

13 files changed

+969
-22
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [1.7.2] - 2021-05-23
4+
5+
- Added a new option to ignore rules already prefixed
6+
37
## [1.6.9] - 2021-05-12
48

59
- Fix a bug with :root and html rules

README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ All the options are optional, and a default value will be used, if any of them i
296296
| rtlPrefix | `string` or `string[]` | `[dir="rtl"]` | Prefix to use in the right-to-left CSS rules |
297297
| bothPrefix | `string` or `string[]` | `[dir]` | Prefix to use for styles in both directions when the specificity of the ltr or rtl styles will override them |
298298
| safeBothPrefix | `boolean` | `false` | Add the `bothPrefix` to those declarations that can be flipped to avoid them being overridden by specificity |
299+
| ignorePrefixedRules| `boolean` | true | Ignores rules that have been prefixed with some of the prefixes contained in `ltrPrefix`, `rtlPrefix`, or `bothPrefix` |
299300
| source | `Source (string)` | `Source.ltr` | The direction from which the final CSS will be generated |
300301
| processUrls | `boolean` | `false` | Change the strings using the string map also in URLs |
301302
| processKeyFrames | `boolean` | `false` | Flip keyframe animations |
@@ -565,6 +566,70 @@ As `test2` has the same level of specificity as `test1`, now the result is that
565566
566567
---
567568
569+
#### ignorePrefixedRules
570+
571+
<details><summary>Expand</summary>
572+
<p>
573+
574+
This option is to ignore the rules that have been prefixed with one of the prefixes contained in `ltrPrefix`, `rtlPrefix`, or `bothPrefix`:
575+
576+
##### input
577+
578+
```css
579+
[dir="ltr"] test {
580+
left: 10px;
581+
}
582+
[dir="rtl"] test {
583+
right: 10px;
584+
}
585+
```
586+
587+
##### ignorePrefixedRules true
588+
589+
```javascript
590+
const options = { ignorePrefixedRules: true }; // This is the default value
591+
```
592+
593+
##### output
594+
595+
```css
596+
[dir="ltr"] test {
597+
left: 10px;
598+
}
599+
[dir="rtl"] test {
600+
right: 10px;
601+
}
602+
```
603+
604+
##### ignorePrefixedRules false
605+
606+
```javascript
607+
const options = { ignorePrefixedRules: false };
608+
```
609+
610+
##### output
611+
612+
```css
613+
[dir="ltr"] [dir="ltr"] test {
614+
left: 10px;
615+
}
616+
[dir="rtl"] [dir="ltr"] test {
617+
right: 10px;
618+
}
619+
[dir="ltr"] [dir="rtl"] test {
620+
right: 10px;
621+
}
622+
[dir="rtl"] [dir="rtl"] test {
623+
left: 10px;
624+
}
625+
```
626+
627+
</p>
628+
629+
</details>
630+
631+
---
632+
568633
#### source
569634
570635
<details><summary>Expand</summary>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "postcss-rtlcss",
3-
"version": "1.6.9",
3+
"version": "1.7.2",
44
"description": "PostCSS plugin to build Cascading Style Sheets (CSS) with Left-To-Right (LTR) and Right-To-Left (RTL) rules",
55
"keywords": [
66
"postcss",

src/@types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export interface PluginOptions {
5151
rtlPrefix?: strings;
5252
bothPrefix?: strings;
5353
safeBothPrefix?: boolean;
54+
ignorePrefixedRules?: boolean;
5455
source?: SourceValues;
5556
processUrls?: boolean;
5657
processKeyFrames?: boolean;

src/constants/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export const RTL_CONTROL_DIRECTIVE_REG_EXP = /^\/\*!? *rtl:?(begin|end)?:(\w+):?
1313
export const FLIP_PROPERTY_REGEXP = /(right|left)/i;
1414
export const HTML_SELECTOR_REGEXP = /^(html)(?=\W|$)/;
1515
export const ROOT_SELECTOR_REGEXP = /(:root)(?=\W|$)/;
16+
export const REG_EXP_CHARACTERS_REG_EXP = /[.?*+^$[\]\\(){}|-]/g;
17+
export const LAST_WORD_CHARACTER_REG_EXP = /\w$/;
1618

1719
export enum CONTROL_DIRECTIVE {
1820
IGNORE = 'ignore',

src/data/store.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import {
1616
PluginStringMap
1717
} from '@types';
1818
import { getKeyFramesStringMap, getKeyFramesRegExp } from '@parsers/atrules';
19-
import { BOOLEAN_TYPE } from '@constants';
19+
import {
20+
BOOLEAN_TYPE,
21+
REG_EXP_CHARACTERS_REG_EXP,
22+
LAST_WORD_CHARACTER_REG_EXP
23+
} from '@constants';
2024

2125
interface Store {
2226
options: PluginOptionsNormalized;
@@ -25,8 +29,11 @@ interface Store {
2529
keyframesRegExp: RegExp;
2630
rules: RulesObject[];
2731
rulesAutoRename: Rule[];
32+
rulesPrefixRegExp: RegExp;
2833
}
2934

35+
const defaultRegExp = new RegExp('$-^');
36+
3037
const defaultStringMap = [
3138
{
3239
name: 'left-right',
@@ -91,12 +98,35 @@ const isNotAcceptedStringMap = (stringMap: PluginStringMap[]): boolean => {
9198
);
9299
};
93100

101+
const spreadArrayOfStrings = (arr: string[], item: strings): string[] => {
102+
return typeof item === 'string'
103+
? [...arr, item]
104+
: [...arr, ...item];
105+
};
106+
107+
const createRulesPrefixesRegExp = (options: PluginOptionsNormalized): RegExp => {
108+
const { ltrPrefix, rtlPrefix, bothPrefix, ignorePrefixedRules } = options;
109+
if (!ignorePrefixedRules) return defaultRegExp;
110+
let prefixes: string[] = [];
111+
prefixes = spreadArrayOfStrings(prefixes, ltrPrefix);
112+
prefixes = spreadArrayOfStrings(prefixes, rtlPrefix);
113+
prefixes = spreadArrayOfStrings(prefixes, bothPrefix);
114+
prefixes = prefixes.map((p: string): string => {
115+
const escaped = p.replace(REG_EXP_CHARACTERS_REG_EXP, '\\$&');
116+
return LAST_WORD_CHARACTER_REG_EXP.test(p)
117+
? `${escaped}(?:\\W|$)`
118+
: escaped;
119+
});
120+
return new RegExp(`(${prefixes.join('|')})`);
121+
};
122+
94123
const defaultOptions = (): PluginOptionsNormalized => ({
95124
mode: Mode.combined,
96125
ltrPrefix: '[dir="ltr"]',
97126
rtlPrefix: '[dir="rtl"]',
98127
bothPrefix: '[dir]',
99128
safeBothPrefix: false,
129+
ignorePrefixedRules: true,
100130
source: Source.ltr,
101131
processUrls: false,
102132
processKeyFrames: false,
@@ -106,15 +136,14 @@ const defaultOptions = (): PluginOptionsNormalized => ({
106136
greedy: false
107137
});
108138

109-
const defaultKeyframesRegExp = new RegExp('$-^');
110-
111139
const store: Store = {
112140
options: {...defaultOptions()},
113141
keyframes: [],
114142
keyframesStringMap: {},
115-
keyframesRegExp: defaultKeyframesRegExp,
143+
keyframesRegExp: defaultRegExp,
116144
rules: [],
117-
rulesAutoRename: []
145+
rulesAutoRename: [],
146+
rulesPrefixRegExp: defaultRegExp
118147
};
119148

120149
export const normalizeOptions = (options: PluginOptions): PluginOptionsNormalized => {
@@ -128,6 +157,9 @@ export const normalizeOptions = (options: PluginOptions): PluginOptionsNormalize
128157
if (options.autoRename && AutorenameValuesArray.includes(options.autoRename)) {
129158
returnOptions.autoRename = options.autoRename;
130159
}
160+
if (typeof options.ignorePrefixedRules === BOOLEAN_TYPE) {
161+
returnOptions.ignorePrefixedRules = options.ignorePrefixedRules;
162+
}
131163
if (typeof options.greedy === BOOLEAN_TYPE) {
132164
returnOptions.greedy = options.greedy;
133165
}
@@ -171,9 +203,10 @@ const initStore = (options: PluginOptions): void => {
171203
store.options = normalizeOptions(options);
172204
store.keyframes = [];
173205
store.keyframesStringMap = {};
174-
store.keyframesRegExp = defaultKeyframesRegExp;
206+
store.keyframesRegExp = defaultRegExp;
175207
store.rules = [];
176208
store.rulesAutoRename = [];
209+
store.rulesPrefixRegExp = createRulesPrefixesRegExp(store.options);
177210
};
178211

179212
const initKeyframesData = (): void => {

src/parsers/rules.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from '@utilities/directives';
1010
import { walkContainer } from '@utilities/containers';
1111
import { cleanRuleRawsBefore } from '@utilities/rules';
12-
import { addSelectorPrefixes } from '@utilities/selectors';
12+
import { addSelectorPrefixes, hasSelectorsPrefixed } from '@utilities/selectors';
1313
import { parseDeclarations } from './declarations';
1414

1515
export const parseRules = (
@@ -77,19 +77,23 @@ export const parseRules = (
7777
);
7878

7979
if (checkDirective(controlDirectives, CONTROL_DIRECTIVE.RENAME)) {
80-
store.rulesAutoRename.push(rule);
81-
parseDeclarations(
82-
rule,
83-
hasParentRule,
84-
sourceDirectiveValue,
85-
true
86-
);
80+
if (!hasSelectorsPrefixed(rule)) {
81+
store.rulesAutoRename.push(rule);
82+
parseDeclarations(
83+
rule,
84+
hasParentRule,
85+
sourceDirectiveValue,
86+
true
87+
);
88+
}
8789
} else {
88-
parseDeclarations(
89-
rule,
90-
hasParentRule,
91-
sourceDirectiveValue
92-
);
90+
if (!hasSelectorsPrefixed(rule)) {
91+
parseDeclarations(
92+
rule,
93+
hasParentRule,
94+
sourceDirectiveValue
95+
);
96+
}
9397
}
9498

9599
parseRules(

src/utilities/selectors.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,29 @@ const addPrefix = (prefix: string, selector: string): string => {
1515

1616
export const addSelectorPrefixes = (rule: Rule, prefixes: strings): void => {
1717
rule.selectors = typeof prefixes === 'string'
18-
? rule.selectors.map((selector: string): string => addPrefix(prefixes, selector))
18+
? rule.selectors.map((selector: string): string => {
19+
if (store.rulesPrefixRegExp.test(selector)) {
20+
return selector;
21+
}
22+
return addPrefix(prefixes, selector);
23+
})
1924
: rule.selectors.reduce((selectors: string[], selector: string): string[] => {
20-
selectors = selectors.concat(prefixes.map((prefix: string): string => addPrefix(prefix, selector)));
25+
if (store.rulesPrefixRegExp.test(selector)) {
26+
selectors = [...selectors, selector];
27+
} else {
28+
selectors = selectors.concat(
29+
prefixes.map((prefix: string): string => addPrefix(prefix, selector))
30+
);
31+
}
2132
return selectors;
2233
}, []);
2334
};
2435

36+
export const hasSelectorsPrefixed = (rule: Rule): boolean => {
37+
const prefixed = rule.selectors.find((selector: string) => store.rulesPrefixRegExp.test(selector));
38+
return !!prefixed;
39+
};
40+
2541
export const addProperSelectorPrefixes = (
2642
ruleFlipped: Rule,
2743
ruleFlippedSecond: Rule,

0 commit comments

Comments
 (0)