Skip to content
This repository was archived by the owner on Jan 30, 2025. It is now read-only.

Commit 8971655

Browse files
committed
feat(helper.ts): Added has pattern and type guard
1 parent 6426552 commit 8971655

File tree

1 file changed

+124
-12
lines changed

1 file changed

+124
-12
lines changed

src/_internal/utils/helper.ts

Lines changed: 124 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,48 @@
11
import path from 'path';
2-
import htmltags from './htmltags';
3-
import { ClassesObjectType } from '..';
2+
import type { ClassesObjectType, HasEPE, HasECE, HasCPC, HasCCC } from '..';
3+
import htmlTags from './html-tags';
44

55
export const isWindowDefined = typeof window !== 'undefined';
66
export const isDocumentDefined = typeof document !== 'undefined';
77
export const isInDevelopment = process.env.NODE_ENV === 'development';
88

9-
export const isClassesObjectType = (object: any): object is ClassesObjectType => {
9+
export const isClassesObjectType = (object: object): object is ClassesObjectType => {
1010
return typeof object === 'object' && !Array.isArray(object);
1111
};
1212

13+
export const toPascalCase = (str: string) => {
14+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
15+
};
16+
17+
const pascalCaseHtmlTags = htmlTags.map((code) => toPascalCase(code));
18+
19+
const isHasEPEType = (property: string): property is HasEPE => {
20+
const regex = new RegExp(`^has(${pascalCaseHtmlTags.join('|')})Plus(${pascalCaseHtmlTags.join('|')})?(.*?)$`);
21+
return regex.test(property);
22+
};
23+
const isHasECEType = (property: string): property is HasECE => {
24+
const regex = new RegExp(`^has(${pascalCaseHtmlTags.join('|')})Child(${pascalCaseHtmlTags.join('|')})?(.*?)$`);
25+
return regex.test(property);
26+
};
27+
28+
function isHasCPCTypeKey(key: string): key is HasCPC {
29+
const regex = /^hasClass.*Plus.*$/;
30+
return regex.test(key);
31+
}
32+
33+
function isHasCPCType(property: string): property is HasCPC {
34+
return isHasCPCTypeKey(property);
35+
}
36+
37+
function isHasCCCTypeKey(key: string): key is HasCPC {
38+
const regex = /^hasClass.*Child.*$/;
39+
return regex.test(key);
40+
}
41+
42+
function isHasCCCType(property: string): property is HasCCC {
43+
return isHasCCCTypeKey(property);
44+
}
45+
1346
// Block the bug selector if the property is only numbers.
1447
export const isNumeric = (value: string): boolean => {
1548
return /^\d+$/.test(value);
@@ -23,25 +56,104 @@ export const get = {
2356
dir,
2457
};
2558

26-
export const toPascalCase = (str: string) => {
27-
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
28-
};
29-
3059
export const camelToKebabCase = (property: string) => {
31-
const pseudoCamelPropWithArgs = ['nthChild', 'nthLastChild', 'nthLastOfType', 'nthOfType', 'lang', 'not'];
60+
const pseudoCamelPropWithArgs = [
61+
'nthChild',
62+
'nthLastChild',
63+
'nthLastOfType',
64+
'nthOfType',
65+
'lang',
66+
'notClass',
67+
'not',
68+
'hasClassChild',
69+
'hasClassPlus',
70+
'hasClass',
71+
'hasChild',
72+
'hasPlus',
73+
'has',
74+
];
3275

3376
const toKebabCase = (str: string) => str.replace(/([A-Z])/g, '-$1').toLowerCase();
3477

3578
for (const prop of pseudoCamelPropWithArgs) {
3679
const index = property.indexOf(prop);
3780
if (index !== -1) {
3881
const afterProp = property.slice(index + prop.length);
82+
const afterNotKebab = afterProp.replace(/_/g, '-').toLowerCase();
83+
84+
if (prop === 'notClass') {
85+
return `:not(.${afterNotKebab})`;
86+
}
3987

4088
if (prop === 'not') {
41-
const afterNot = afterProp.charAt(0).toUpperCase() + afterProp.slice(1).toLowerCase();
42-
const afterNotKebab = afterProp.replace(/_/g, '-').toLowerCase();
43-
if (!htmltags.includes(afterNot)) {
44-
return `:not(.${afterNotKebab})`;
89+
if (property.includes('not(')) {
90+
return `:not${afterProp.toLowerCase()}`;
91+
} else {
92+
return `:not(${afterProp.toLowerCase()})`;
93+
}
94+
}
95+
96+
if (isHasCCCType(property)) {
97+
const regex = /^hasClass(.*?)Child(.*?)$/;
98+
const matches = property.match(regex);
99+
if (matches) {
100+
const [, class1, class2] = matches;
101+
return `:has(.${class1.replace(/_/g, '-').toLowerCase()} > ${pascalCaseHtmlTags.includes(class2) ? class2.toLowerCase() : '.' + class2.replace(/_/g, '-').toLowerCase()})`;
102+
}
103+
}
104+
105+
if (isHasCPCType(property)) {
106+
const regex = /^hasClass(.*?)Plus(.*?)$/;
107+
const matches = property.match(regex);
108+
if (matches) {
109+
const [, class1, class2] = matches;
110+
return `:has(.${class1.replace(/_/g, '-').toLowerCase()} + ${pascalCaseHtmlTags.includes(class2) ? class2.toLowerCase() : '.' + class2.replace(/_/g, '-').toLowerCase()})`;
111+
}
112+
}
113+
114+
if (prop === 'hasClassChild') {
115+
return `:has(> .${afterNotKebab})`;
116+
}
117+
118+
if (prop === 'hasClassPlus') {
119+
return `:has(+ .${afterNotKebab})`;
120+
}
121+
122+
if (prop === 'hasClass') {
123+
return `:has(.${afterNotKebab})`;
124+
}
125+
126+
if (isHasECEType(property)) {
127+
const regex = /^has(.*?)Child(.*?)$/;
128+
const matches = property.match(regex);
129+
if (matches) {
130+
const [, tag1, tag2] = matches;
131+
return `:has(${tag1.toLowerCase()} > ${pascalCaseHtmlTags.includes(tag2) ? tag2.toLowerCase() : '.' + tag2.replace(/_/g, '-').toLowerCase()})`;
132+
}
133+
}
134+
135+
if (isHasEPEType(property)) {
136+
const regex = /^has(.*?)Plus(.*?)$/;
137+
const matches = property.match(regex);
138+
if (matches) {
139+
const [, tag1, tag2] = matches;
140+
return `:has(${tag1.toLowerCase()} + ${pascalCaseHtmlTags.includes(tag2) ? tag2.toLowerCase() : '.' + tag2.replace(/_/g, '-').toLowerCase()})`;
141+
}
142+
}
143+
144+
if (prop === 'hasChild') {
145+
return `:has(> ${afterProp.toLowerCase()})`;
146+
}
147+
148+
if (prop === 'hasPlus') {
149+
return `:has(+ ${afterProp.toLowerCase()})`;
150+
}
151+
152+
if (prop === 'has') {
153+
if (property.includes('has(')) {
154+
return `:has${afterProp.toLowerCase()}`;
155+
} else {
156+
return `:has(${afterProp.toLowerCase()})`;
45157
}
46158
}
47159

0 commit comments

Comments
 (0)