Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5",
"printWidth": 80
}
33 changes: 21 additions & 12 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"cssModulesIntellisense.aliases": {
"~": "./src"
},
"cSpell.words": ["onig", "ovsx", "pcss", "styl", "vsctm"]
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"cssModulesIntellisense.aliases": {
"~": "./src"
},
"cSpell.words": [
"garg",
"lokesh",
"onig",
"ovsx",
"pcss",
"styl",
"vscodeignore",
"vsctm"
]
}
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Fixed

- Minor Bugs

## [0.1.5] – 2026-01-27

### Added
Expand Down Expand Up @@ -135,5 +139,4 @@ All notable changes to this project will be documented in this file.
[0.1.2]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.1...v0.1.2
[0.1.3]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.2...v0.1.3
[0.1.4]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.3...v0.1.4

[0.1.5]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.4...v0.1.5
10 changes: 6 additions & 4 deletions docs/PROJECT_STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CSS/SCSS Modules IntelliSense VS Code extension.

## Directory Structure

```
```txt
css-modules-intellisense/
├── .github/ # GitHub-specific files
│ └── workflows/ # GitHub Actions workflows
Expand Down Expand Up @@ -33,7 +33,8 @@ css-modules-intellisense/
│ │ ├── classNameCache.ts # Class name caching
│ │ ├── cssModuleDependencyCache.ts
│ │ ├── loadCaches.ts # Cache initialization
│ │ └── processConfig.ts # Configuration processing
│ │ ├── processConfig.ts # Configuration processing
│ │ └── vsConfig.ts # VS Code configuration helpers
│ ├── providers/ # Language feature providers
│ │ ├── completionProvider.ts # Auto-completion
│ │ ├── definitionProvider.ts # Go-to-definition
Expand All @@ -55,8 +56,7 @@ css-modules-intellisense/
│ │ ├── getPath.ts
│ │ ├── getRegistry.ts
│ │ ├── isDocumentModule.ts
│ │ ├── isPositionInComment.ts
│ │ ├── isPositionInString.ts
│ │ ├── isPositionInScope.ts
│ │ └── sanitizeCssInput.ts
│ ├── config.ts # Extension configuration
│ └── extension.ts # Extension entry point
Expand All @@ -70,6 +70,7 @@ css-modules-intellisense/
│ └── scss.tmLanguage.json
├── .editorconfig # Editor configuration
├── .gitignore # Git ignore rules
├── .prettierrc # Prettier formatting rules
├── .markdownlint.json # Markdown linting rules
├── .vscode-test.mjs # VS Code test configuration
├── .vscodeignore # Files to exclude from extension package
Expand Down Expand Up @@ -120,6 +121,7 @@ Static resources including test fixtures and extension icons
## Configuration Files

- `.editorconfig` - Code style consistency
- `.prettierrc` - Code formatting rules
- `tsconfig.json` - TypeScript compiler settings
- `eslint.config.mjs` - Linting rules
- `package.json` - Extension metadata and dependencies
17 changes: 15 additions & 2 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,25 @@

## Enhancements

<!-- Add enhancements here -->
- Add `cssModulesIntellisense.diagnostics.classNotDefined.enabled` (boolean) and
`cssModulesIntellisense.diagnostics.classNotDefined.severity`
(`"error" | "warning" | "info" | "hint"`)
settings to control the "class not defined" diagnostic in `analyzeDocument`.

- Add `cssModulesIntellisense.diagnostics.classNotUsed.enabled` (boolean) and
`cssModulesIntellisense.diagnostics.classNotUsed.severity`
(`"error" | "warning" | "info" | "hint"`)
settings to show a "Class 'x' is never used" diagnostic on CSS module files,
by cross-referencing `ClassNameCache` against usage in all dependent JS/TS files
via `CssModuleDependencyCache`.

## Known Issues

<!-- Add known issues here -->

## Future Improvements

<!-- Add future improvements here -->
- Add an extension setting to configure the class name cache size (LRU max entries).
Currently hardcoded to 3, which causes frequent cache evictions and re-parsing
in projects with more than 3 CSS module files. Should be user-configurable via
`cssModules.classNameCacheSize` in extension settings.
86 changes: 68 additions & 18 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,78 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";

export default [{
files: ["**/*.ts"],
}, {
export default [
{
ignores: ["dist/**", "node_modules/**", "assets/**", ".vscode-test/**"],
},
{
files: ["src/**/*.ts"],
plugins: {
"@typescript-eslint": typescriptEslint,
"@typescript-eslint": typescriptEslint,
},

languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},

rules: {
"@typescript-eslint/naming-convention": ["warn", {
selector: "import",
format: ["camelCase", "PascalCase"],
}],
// Naming
"@typescript-eslint/naming-convention": [
"warn",
{ selector: "import", format: ["camelCase", "PascalCase"] },
],

// Correctness
curly: "warn",
eqeqeq: ["warn", "always"],
"@typescript-eslint/only-throw-error": "warn",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/await-thenable": "error",

curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
// Type safety
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-return": "warn",

// Dead code
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"no-unused-vars": "off", // disabled in favour of @typescript-eslint/no-unused-vars

// Code style
semi: "warn",
"@typescript-eslint/consistent-type-imports": [
"warn",
{ prefer: "type-imports", fixStyle: "inline-type-imports" },
],
"@typescript-eslint/prefer-nullish-coalescing": "warn",
"@typescript-eslint/prefer-optional-chain": "warn",
},
},
{
// Looser rules for config/test files — no type-aware linting needed
files: ["*.mjs", "src/test/**/*.ts"],
plugins: {
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
curly: "warn",
eqeqeq: "warn",
semi: "warn",
},
}];
},
];
4 changes: 2 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export async function activate(context: vscode.ExtensionContext) {
CheckDocument.diagnosticCollection = diagnosticCollection;

Cache.context = context;
const loaded = Cache.loadCache();
const loaded = await Cache.loadCache();
if (!loaded) {
CssModuleDependencyCache.populateCacheFromWorkspace();
await CssModuleDependencyCache.populateCacheFromWorkspace();
}
loadCaches();

Expand Down
12 changes: 6 additions & 6 deletions src/libs/cache.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as fs from "fs";
import * as path from "path";
import * as vscode from "vscode";
import type * as vscode from "vscode";
import { CSS_MODULES_CACHE_FILENAME, DEBOUNCE_TIMER } from "../config";
import {
CacheJsonObject,
type CacheJsonObject,
ClassNameCache,
ClassNameRangeMap,
ModulePathCache,
Expand Down Expand Up @@ -79,7 +79,7 @@
static async saveCache() {
clearTimeout(this.saveCacheDebounceId);
this.saveCacheDebounceId = setTimeout(() => {
this._saveCache();
this._saveCache().catch(console.error);
}, DEBOUNCE_TIMER.CACHE);
}

Expand Down Expand Up @@ -139,13 +139,13 @@

try {
const raw = fs.readFileSync(cacheFilePath, "utf-8");
const parsed: { [K in keyof CacheJsonObject]?: CacheJsonObject[K] } =

Check warning on line 142 in src/libs/cache.ts

View workflow job for this annotation

GitHub Actions / test (1.93.0)

Unsafe assignment of an `any` value
JSON.parse(raw);

this.pathMapCache.setArray(parsed.pathMapCache || []);
this.pathMapCache.setArray(parsed.pathMapCache ?? []);

this.modulePathCache.setMap(
Object.entries(parsed.modulePathCache || {}).map(
Object.entries(parsed.modulePathCache ?? {}).map(
([key, valueArray]) => [
key,
new ModulePathCacheSet(this.pathMapCache, valueArray),
Expand All @@ -154,7 +154,7 @@
);

this.classNameCache.setMap(
Object.entries(parsed.classNameCache || {}).map(([key, value]) => [
Object.entries(parsed.classNameCache ?? {}).map(([key, value]) => [
key,
new ClassNameRangeMap(Object.entries(value)),
])
Expand Down
5 changes: 2 additions & 3 deletions src/libs/checkDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default class CheckDocument {
static push(document: vscode.TextDocument): number {
if (this.isQueueEmpty()) {
const length = this.documentQueue.push(document);
this.checkNextDocument();
this.checkNextDocument().catch(console.error);
return length;
}
while (this.documentQueue.length >= MAX_CHECK_DOCUMENT_QUEUE_LENGTH) {
Expand Down Expand Up @@ -109,7 +109,7 @@ export default class CheckDocument {
static setDebounceTimer(): void {
clearTimeout(this.debounceTimerId);
this.debounceTimerId = setTimeout(() => {
this.checkNextDocument();
this.checkNextDocument().catch(console.error);
}, DEBOUNCE_TIMER.CHECK_DOCUMENT);
}

Expand Down Expand Up @@ -137,7 +137,6 @@ export default class CheckDocument {
return;
}

const text = document.getText();
const diagnostics: vscode.Diagnostic[] = [];
const importMatches = await getAllImportModulePaths(document);

Expand Down
42 changes: 24 additions & 18 deletions src/libs/classNameCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import {
resolveWorkspaceRelativePath,
} from "../utils/getPath";
import { sanitizeCssInput } from "../utils/sanitizeCssInput";
import isPositionInComment from "../utils/isPositionInComment";
import { isPositionInComment } from "../utils/isPositionInScope";
import CssModuleDependencyCache from "./cssModuleDependencyCache";
import CheckDocument from "./checkDocument";
import { ClassNameRange, ClassNameRangeMap } from "../types/cache";
import { type ClassNameRange, ClassNameRangeMap } from "../types/cache";

/**
* A utility class to extract and cache class names from CSS Module files.
Expand All @@ -34,23 +34,29 @@ export default class ClassNameCache {
static async updateClassNameCache(e: vscode.TextDocument) {
const importPath = getWorkspaceRelativeUriPath(e.uri);
clearTimeout(this.ClassNameCacheDebounceIdMap[importPath]);
this.ClassNameCacheDebounceIdMap[importPath] = setTimeout(async () => {
await ClassNameCache.extractFromUri(e.uri);
this.ClassNameCacheDebounceIdMap[importPath] = setTimeout(() => {
(async () => {
await ClassNameCache.extractFromUri(e.uri);

if (SUPPORTED_MODULES.includes(e.languageId)) {
const dependents = CssModuleDependencyCache.getDependentsForDocument(e);
if (SUPPORTED_MODULES.includes(e.languageId)) {
const dependents =
CssModuleDependencyCache.getDependentsForDocument(e);

for (const workspacePath of dependents) {
const resolvedPath = resolveWorkspaceRelativePath(workspacePath);
if (!resolvedPath) {
return;
for (const workspacePath of dependents) {
const resolvedPath = resolveWorkspaceRelativePath(workspacePath);
if (!resolvedPath) {
continue;
}
const document =
await vscode.workspace.openTextDocument(resolvedPath);
CheckDocument.push(document);
}
const document = await vscode.workspace.openTextDocument(
resolvedPath
);
CheckDocument.push(document);
}
}
})()
.catch(console.error)
.finally(() => {
delete ClassNameCache.ClassNameCacheDebounceIdMap[importPath];
});
}, DEBOUNCE_TIMER.UPDATE_CLASS_NAME);
}

Expand Down Expand Up @@ -182,11 +188,11 @@ export default class ClassNameCache {
): Promise<string[] | undefined> {
if (Cache.classNameCache.hasByKey(importPath)) {
return Array.from(
Cache.classNameCache.getByKey(importPath)?.keys() || []
Cache.classNameCache.getByKey(importPath)?.keys() ?? []
);
} else {
return Array.from(
(await this.extractAndCacheClassNames(importPath))?.keys() || []
(await this.extractAndCacheClassNames(importPath))?.keys() ?? []
);
}
}
Expand Down Expand Up @@ -279,7 +285,7 @@ export default class ClassNameCache {
}

Cache.classNameCache.setByKey(importPath, classNames);
Cache.saveCache();
Cache.saveCache().catch(console.error);
return classNames;
}
}
Loading
Loading