Skip to content

Commit

Permalink
feat: diagnostics for css documents (#149)
Browse files Browse the repository at this point in the history
  • Loading branch information
Viijay-Kr authored Apr 25, 2024
1 parent dcf2f33 commit 14c3e92
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-tigers-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-ts-css": patch
---

feat: diagnostics for css documents
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"rules": {
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/semi": "warn",
"curly": "warn",
"curly": "off",
"eqeqeq": "warn",
"no-throw-literal": "warn",
"semi": "off"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ See how it compares with **CSS modules**

- Useful diagnostics information are provided for missing selector - [demo](https://github.com/Viijay-Kr/react-ts-css/tree/main/assets/missing-selector.png)
- Module not found error is also provided for non existing CSS modules - [demo](https://github.com/Viijay-Kr/react-ts-css/tree/main/assets/missing-module.png)
- Hints for un used selectors inside CSS/SCSS documents - [demo](/assets/css-diagnostics.gif)
- Settings to change diagnostics
- `reactTsScss.diagnostics` - Toggle to turn off diagnostics
- `reactTsScss.tsconfig` - Base TS Config path in the project. Useful for resolving path aliases. Defaults to './tsconfig.json'
Expand Down
Binary file added assets/css-diagnostics.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions examples/react-app/src/styles/button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

.btn-secondary {
background: #0019;

.btn-secondary-nested {
background: cyan;
}

}

.btn-secondary-nested {
background: cyan;
}
56 changes: 55 additions & 1 deletion src/providers/css/CSSProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
LocationLink,
Location,
Uri,
WorkspaceEdit,
DiagnosticSeverity,
} from "vscode";
import {
createStyleSheet,
Expand Down Expand Up @@ -40,6 +40,7 @@ import { isIdentifier, isStringLiteral } from "@babel/types";
import path = require("path");
import { ReferenceCodeLens } from "./codelens";
import { readFile } from "fs/promises";
import { extended_Diagnostic } from "../diagnostics";

type CSSProviderOptions = {
providerKind: ProviderKind;
Expand Down Expand Up @@ -517,3 +518,56 @@ export class CSSRenameProvider extends CSSProvider {
return candidates;
}
}

export class CSSDiagnosticsProvider extends CSSProvider {
protected referenceProvider: CSSReferenceProvider;
constructor(options: CSSProviderOptions) {
super(options);
this.referenceProvider = new CSSReferenceProvider(options);
}
async provideDiagnostics(): Promise<Array<extended_Diagnostic>> {
const references = await this.getReferences();
const filePath = normalizePath(this.document.uri.fsPath);
const source_css_file = Store.cssModules.get(filePath);
const selectors = (await parseCss(source_css_file ?? ""))?.selectors;
if (!selectors) return [];

const candidates: extended_Diagnostic[] = [];

for (const selector of selectors.values()) {
let doesExist = false;
for (const ref of references) {
if (ref.status === "fulfilled") {
const parsedResult = ref.value.parsed_result?.parsedResult;

if (!parsedResult) continue;

for (const accessor of parsedResult.style_accessors) {
let _selector;
if (isStringLiteral(accessor.property)) {
_selector = accessor.property.value;
} else if (isIdentifier(accessor.property)) {
_selector = accessor.property.name;
}
if (_selector === selector.selector) {
doesExist = true;
break;
}
}
}
}
if (!doesExist) {
candidates.push({
range: toVsCodeRange(selector.range),
message: `Selector '${selector.selector}' is declared but it's never used`,
source: "(React TS CSS)",
sourceAtRange: selector.selector,
severity: DiagnosticSeverity.Hint,
});
}
}
return candidates;
}
}

export class CodeActionsProvider extends CSSProvider {}
47 changes: 35 additions & 12 deletions src/providers/ts/diagnostics.ts → src/providers/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ import {
Location,
Position,
Range,
TextDocument,
Uri,
} from "vscode";
import { CssModuleExtensions, CSS_MODULE_EXTENSIONS } from "../../constants";
import { Selector, parseCss } from "../../parser/v2/css";
import Store from "../../store/Store";
import { normalizePath } from "../../path-utils";
import { Parser } from "../../parser/Parser";
import { CssModuleExtensions, CSS_MODULE_EXTENSIONS } from "../constants";
import { Selector, parseCss } from "../parser/v2/css";
import Store from "../store/Store";
import { normalizePath } from "../path-utils";
import { Parser } from "../parser/Parser";
import { CSSDiagnosticsProvider } from "./css/CSSProvider";
import { ProviderKind } from "./types";

export type extended_Diagnostic = Diagnostic & {
replace?: string;
Expand All @@ -31,6 +34,7 @@ export type DiagnosticsContext = {
baseDir?: string | undefined;
activeFileDir: string | undefined;
activeFileUri: Uri;
document: TextDocument;
};

export enum DiagnosticCodeActions {
Expand All @@ -43,34 +47,38 @@ export enum DiagnosticNonCodeActions {
}

export class DiagnosticsProvider {
selectorDiagnostics: SelectorRelatedDiagnostics;
importDiagnostics: ImportsRelatedDiagnostics;
protected selectorDiagnostics: SelectorRelatedDiagnostics;
protected importDiagnostics: ImportsRelatedDiagnostics;
protected cssDiagnostics: CSSDocumentDiagnostics;
static diagnosticCollection =
languages.createDiagnosticCollection("react-ts-css");
activeFileUri: Uri;

constructor(context: DiagnosticsContext) {
this.selectorDiagnostics = new SelectorRelatedDiagnostics(context);
this.importDiagnostics = new ImportsRelatedDiagnostics(context);
this.cssDiagnostics = new CSSDocumentDiagnostics(context);
this.activeFileUri = context.activeFileUri;
}

public async runDiagnostics() {
public async runDiagnostics() {}

public async provideDiagnostics() {
await this.selectorDiagnostics.runDiagnostics();
this.importDiagnostics.runDiagnostics();
}

public provideDiagnostics() {
await this.cssDiagnostics.runDiagnostics();
DiagnosticsProvider.diagnosticCollection.set(this.activeFileUri, [
...this.importDiagnostics.diagnostics,
...this.selectorDiagnostics.diagnostics,
...this.cssDiagnostics.diagnostics,
]);
}

public getDiagnostics() {
return [
...this.selectorDiagnostics.diagnostics,
...this.importDiagnostics.diagnostics,
...this.cssDiagnostics.diagnostics,
];
}
}
Expand All @@ -96,7 +104,6 @@ export class SelectorRelatedDiagnostics extends Diagnostics {
}
}
}
renameSelector() {}
async runDiagnostics() {
const {
parser: { parsed_result },
Expand Down Expand Up @@ -227,3 +234,19 @@ export class ImportsRelatedDiagnostics extends Diagnostics {
}
}
}

export class CSSDocumentDiagnostics extends Diagnostics {
protected cssDiagnosticsProvider: CSSDiagnosticsProvider;
constructor(options: DiagnosticsContext) {
super(options);
this.cssDiagnosticsProvider = new CSSDiagnosticsProvider({
providerKind: ProviderKind.Diagnostic,
position: new Position(0, 0),
document: options.document,
});
}

async runDiagnostics() {
this.diagnostics = await this.cssDiagnosticsProvider.provideDiagnostics();
}
}
2 changes: 1 addition & 1 deletion src/providers/ts/code-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
DiagnosticCodeActions,
DiagnosticNonCodeActions,
extended_Diagnostic,
} from "./diagnostics";
} from "../diagnostics";

export class DiagnosticCodeAction implements vscode.CodeActionProvider {
public static readonly codeActionKinds = [vscode.CodeActionKind.QuickFix];
Expand Down
1 change: 1 addition & 0 deletions src/providers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export enum ProviderKind {
RenameSelector,
CodeLens,
Invalid,
Diagnostic,
}
12 changes: 6 additions & 6 deletions src/store/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as fsg from "fast-glob";
import { promises as fs_promises } from "node:fs";
import Settings from "../settings";
import { Parser } from "../parser/Parser";
import { DiagnosticsProvider } from "../providers/ts/diagnostics";
import { DiagnosticsProvider } from "../providers/diagnostics";
import { normalizePath } from "../path-utils";

// Full file path of the active opened file
Expand Down Expand Up @@ -289,13 +289,13 @@ export class Store {

const document = this.activeTextEditor.document;
const filePath = document.uri.fsPath;

this.parser.parsed_result = await this.parser?.parse({
filePath,
content: document.getText(),
});
if (Settings.diagnostics) {
return this.provideDiagnostics();
}

if (Settings.diagnostics) return this.provideDiagnostics();
} catch (e) {}
}

Expand All @@ -318,9 +318,9 @@ export class Store {
parser: this.parser,
activeFileDir,
activeFileUri,
document: this.activeTextEditor.document,
});
await this.diagnosticsProvider.runDiagnostics();
this.diagnosticsProvider.provideDiagnostics();
await this.diagnosticsProvider.provideDiagnostics();
return this.diagnosticsProvider.getDiagnostics();
}
}
Expand Down

0 comments on commit 14c3e92

Please sign in to comment.