Skip to content

Commit

Permalink
fix: pseudo class functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Viijay-Kr committed Aug 27, 2024
1 parent 53214ec commit 30dd498
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 43 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ This extension also supports CSS language features which are not supported by bu
> - Nested selectors
> - Suffixed selectors ([SCSS only](https://sass-lang.com/documentation/style-rules/parent-selector#adding-suffixes))
> - Deeply nested suffix selectors
> - Pseudo Classes
> - Pseudo Class functions
>
> Almost all project scaffolders such as Vite, Next.js and CRA add css module declaration to the project by injecting it in a `.d.ts` file (for instance inside `node_modules/vite/client.d.ts` added by Vite). TypeScript treats these definitions as definition provider for Style properties. This results in a useless definition result when VS Code `Go to Definition` is triggered. Check this [issue](https://github.com/Viijay-Kr/react-ts-css/issues/68).
>
Expand All @@ -35,6 +37,8 @@ See how it compares with **CSS modules**
| Completion | Camel Case selectors - ✅<br>Snake Case selectors - ✅<br>Pascal Case selectors - ✅<br>Kebab Case selectors - ✅ | Camel Case selector - ✅<br>Snake Case selectors - ✅<br>Pascal Case selectors - ✅<br>Kebab Case selectors - ✅ |
| Hover | Supported for all types of selectors - ✅ ||
| SCSS Suffix Selectors |||
| Psuedo Classes |||
| Psuedo Class Functions |||
| Less suffix selectors |||
| Selector Diagnostics |||
| Selector References |||
Expand Down
18 changes: 18 additions & 0 deletions examples/react-app/src/test/psuedo-selectors/PsudeoSelectors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import styles from "./PsuedoSelectors.module.css";

function classNames(...args: any[]): string | undefined {
throw new Error("Function not implemented.");
}

export const Foo = ({ isActive }: { isActive: boolean }) => {
return (
<div
className={classNames({
[styles.primary]: true,
[styles.active]: isActive,
}, styles.secondary, styles.where, styles.hello)}
>
foo
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.not {
&:not(.active) {
opacity: 0.5;
}
}


.is {
&:is(.primary, .secondary) {
font-size: 2rem;
color: darkblue;
}
}

.has {
&:has(.inactive) {
color: darkblue;
}
}

.where {
&:where(.hello) {
text-align: center;
}

:hover {
text-emphasis: none;
}
}
76 changes: 33 additions & 43 deletions src/parser/v2/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ export type Selector = {
range: Range;
content: string;
selectionRange: Range;
rule: string;
};

export type Variable = {
Expand All @@ -115,68 +114,59 @@ export type Variable = {

export const getSelectors = (ast: Stylesheet, document: TextDocument) => {
const selectors: Map<string, Selector> = new Map();
const visitedRules: Node[] = [];
const insertSelector = (selectorNode: Node, selector: string) => {
const parentRule = visitedRules[visitedRules.length - 1];

if (!parentRule) return;

const insertSelector = (
selectorNode: Node,
parentNode: Node,
selector: string
) => {
if (selectors.has(selector)) {
return;
}

const range = Range.create(
document.positionAt(selectorNode.offset),
document.positionAt(selectorNode.end)
);
const selectionRange = Range.create(
document.positionAt(parentNode.offset),
document.positionAt(parentNode.end)
document.positionAt(parentRule.offset),
document.positionAt(parentRule.end)
);
selectors.set(selector, {
selector,
range,
content: parentNode.getText(),
content: parentRule.getText(),
selectionRange,
rule: selectorNode.getText(),
});
};

function resolveSelectors(node: Node, parent: Node | null) {
switch (node.type) {
case NodeType.Ruleset: {
const selectorNodeList = (node as RuleSet).getSelectors();
for (const selectorNode of selectorNodeList.getChildren()) {
for (const simpleSelector of selectorNode.getChildren()) {
if (simpleSelector.type === NodeType.SimpleSelector) {
let selector = simpleSelector.getText();
let isInvalid = false;
if (isSuffix(selector)) {
selector =
resolveSuffixSelectors(parent, "") +
selector.replace("&", "");
} else if (isSibling(selector)) {
selector = selector.replace(/&./gi, "");
} else if (isChild(selector)) {
selector = selector.replace(/& ./gi, "");
} else if (isNormal(selector)) {
selector = selector.replace(".", "");
} else {
isInvalid = true;
}
if (isPsuedo(selector)) {
selector = selector.split(":")[0];
}
if (isCombination(selector)) {
for (const _selector of selector.split(".")) {
insertSelector(selectorNode, node, _selector);
}
} else {
if (!isInvalid) {
insertSelector(selectorNode, node, selector);
}
}
}
}
visitedRules.push(node);
break;
}
case NodeType.SimpleSelector: {
let selector = node.getText();
let isInvalid = false;
if (isSuffix(selector)) {
selector = resolveSuffixSelectors(parent, "");
} else if (isSibling(selector)) {
selector = selector.replace(/&./gi, "");
} else if (isChild(selector)) {
selector = selector.replace(/& ./gi, "");
} else if (isNormal(selector)) {
selector = selector.replace(".", "");
} else {
isInvalid = true;
}
// Psuedo attributes is figured out only later. Thus it has to be its own if block
if (isPsuedo(selector)) {
selector = selector.split(":")[0];
}

if (!isInvalid) {
insertSelector(node, selector);
}
break;
}
Expand Down

0 comments on commit 30dd498

Please sign in to comment.